当孩子有身份证时,如何使用EF将多个子实体添加到对象?

我们使用EF5和SQL Server 2012以下两个类:

public class Question { public Question() { this.Answers = new List(); } public int QuestionId { get; set; } public string Title { get; set; } public virtual ICollection Answers { get; set; } } public class Answer { public int AnswerId { get; set; } public string Text { get; set; } public int QuestionId { get; set; } public virtual Question Question { get; set; } } 

映射如下:

 public class AnswerMap : EntityTypeConfiguration { public AnswerMap() { // Primary Key this.HasKey(t => t.AnswerId); // Identity this.Property(t => t.AnswerId) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

数据库DDL

 CREATE TABLE Answer ( [AnswerId] INT IDENTITY (1, 1) NOT NULL, [QuestionId] INT NOT NULL, [Text] NVARCHAR(1000), CONSTRAINT [PK_Answer] PRIMARY KEY CLUSTERED ([AnswerId] ASC) )"; 

以下是我尝试过的结果:

这适用于一个孩子:

 var a = new Answer{ Text = "AA", QuestionId = 14 }; question.Answers.Add(a); _uow.Questions.Update(question); _uow.Commit(); 

这不适用于多个孩子:

错误:ObjectStateManager中已存在具有相同键的对象。 ObjectStateManager无法使用相同的键跟踪多个对象。

 var a = new Answer{ AnswerId = 0, Text = "AAA", QuestionId = 14 }; var b = new Answer { AnswerId = 0, Text = "BBB", QuestionId = 14 }; question.Answers.Add(a); question.Answers.Add(b); _uow.Questions.Update(question); _uow.Commit(); 

这不适用于多个孩子:

它创建了AnswerID的1000和1001但我希望数据库创建新的Id。

 var a = new Answer{ AnswerId = 1000, Text = "AAA", QuestionId = 14 }; var b = new Answer { AnswerId = 1001, Text = "BBB", QuestionId = 14 }; question.Answers.Add(a); question.Answers.Add(b); _uow.Questions.Update(question); _uow.Commit(); 

不起作用:

编译错误。 无法将null转换为int

 var a = new Answer{ AnswerId = null, Text = "AAA", QuestionId = 14 }; var b = new Answer { AnswerId = null, Text = "BBB", QuestionId = 14 }; question.Answers.Add(a); question.Answers.Add(b); _uow.Questions.Update(question); _uow.Commit(); 

不起作用:

ObjectStateManager无法使用相同的键跟踪多个对象。

 var a = new Answer{ Text = "AAA", QuestionId = 14 }; var b = new Answer { Text = "BBB", QuestionId = 14 }; question.Answers.Add(a); question.Answers.Add(b); _uow.Questions.Update(question); _uow.Commit(); 

在我的应用程序中,我在客户端上生成了一个或多个新的Answer对象,然后将这些对象发送到服务器。 上面我模拟了如果不将客户端添加到问题中会发生什么 。 请注意,将所有Answers添加到Question对象是在客户端上完成的,然后以JSON字符串forms提供给服务器。 然后将其反序列化为这样的问题对象:

 public HttpResponseMessage PutQuestion(int id, Question question) { _uow.Questions.Update(question); _uow.Commit(); 

我希望使用new identity ID创建每个Answer对象,以便将这些对象添加到Question对象,并以正常方式返回Question对象。

我不知道如何做到这一点。 到目前为止我所有的简单测试都不起作用。 请注意,这是我们小组成员先前提出的问题的变体,该问题不太明确,我试图关闭。 这个问题我希望更清楚。

笔记:

以下是更新编码的方式:

 public virtual void Update(T entity) { DbEntityEntry dbEntityEntry = DbContext.Entry(entity); if (dbEntityEntry.State == EntityState.Detached) { DbSet.Attach(entity); } dbEntityEntry.State = EntityState.Modified; } 

你有没有提到你要加两次……?!

 question.Answers.Add(a); question.Answers.Add(a); 

通常,要添加其ID为标识的项目,您必须跳过设置ID。 您还应该将[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]属性添加到这些ID:

 public class Answer { [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int AnswerId { get; set; } public string Text { get; set; } public int QuestionId { get; set; } public virtual Question Question { get; set; } } 

并添加如下数据:

 var a = new Answer{ Text = "AAA", QuestionId = 14 }; var b = new Answer { Text = "BBB", QuestionId = 14 }; dbContext.Answers.Add(a); dbContext.Answers.Add(b); dbContext.SaveChanges(); // ... 

我也遇到了同样的身份“限制”。 事实certificate,如果您添加父项和任何子项,EF可以处理父项和子项都被添加到一起的事实。 更新父项并同时插入两个子项时遇到问题。 如果您附加父母,EF将自动接收这两个孩子并附加他们,无论您是否愿意。 由于我们希望它自动生成Id,因此我们不会设置子项的主键。 但是,当父级是更新时,EF无法处理具有相同主键的项目,并且由于两个子级都具有相同的PK 0,因此EF会爆炸。

我找到的唯一方法是手动将子项的ID设置为不同的数字。 我通常将第一个孩子的Id设置为-1,然后将-2设置为第二个孩子,依此类推。 这将导致EF保存子项,并且由于在数据库上运行Identity,密钥将自动更新,因为-1和-2不是有效的标识值。

但是,如果您达到第3级或更高级别,这将导致巨大的痛苦。 你不仅要为每个孩子更新这个PK,而且你必须将其任何一个孩子的FK更新为这个新的-1或-2值。 否则,保存将再次失败!

我看到的唯一其他选项实际上只是一次插入一个子节点并调用save,因此上下文不会处理多个具有相同PK的对象,但这种情况会破坏ORM的目的…

试试这些:

  • 使用DbSetCreate()方法
  • 将新实例添加到ContextAnswers集合中

您已为EF设置了适当的QuestionId以实现关系。 另外,不要将AnswerId明确设置为零。

 var a = new _uow.Answers.Create(); a.Text = "AAA"; a.QuestionId = 14; _uow.Answers.Add(a); var b = new _uow.Answers.Create(); b.Text = "BBB"; b.QuestionId = 14; _uow.Answers.Add(a); 

如果您打算查询Question 14的Answers集合,则可能需要调用_uow.ChangeTracker.DetectChanges()

如果您已正确声明Id为Key和DBGenerated身份。 然后EF将允许您在保存之前将其中许多添加到上下文中。 您不能使用相同的密钥附加项目 。 Attach适用于脱机数据,放在上下文中,设置其状态和保存类型方案。

您已使用相同的实例两次,默认情况下使用EF跟踪会造成混乱。 或者以某种方式使用ATTACH两次。 确保您干净地处理您的实例。 *

例如

 public class BaseEntityLongConfiguration : EntityTypeConfiguration where T : BaseObjectLong { public BaseEntityLongConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) { // Primary Key this.HasKey(t => t.Id); // Properties //Id is an indent allocated by DB this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated this.Property(t => t.RowVersion) // for concurrency .IsRequired() .IsFixedLength() .HasMaxLength(8) .IsRowVersion(); } } 

刚试过一个简单的测试来检查它的工作原理(在ef5中)

 public class ExampleLog { public virtual long Id { get; set; } public virtual string MessageText { get; set; } } [TestMethod] public void ExampleLogTest() { var e1 = new ExampleLog(); e1.MessageText = "example1"; var e2 = new ExampleLog(); e2.MessageText = "example2"; _context.Set().Add(e1); _context.Set().Add(e2); var res = _context.SaveChanges(); Debug.WriteLine("result expected 2->" + res.ToString()); } 

编辑:根据要求,添加save Respository模式,BAsic示例,删除error handling

 public class RepositoryBase : where TPoco : BaseObject { public RepositoryBase(BosBaseDbContext context) { Context = context; } 

….

  ///  /// Add new POCO ///  public virtual OperationResult Add(TPoco poco) { var opResult = new OperationResult(); try { Context.Set().Add(poco); } catch (Exception ex) { .... custom error tool return opResult; } return opResult; } ///  /// Poco must already be attached,, detect chnages is triggered ///  public virtual OperationResult Change(TPoco poco) { var opResult = new OperationResult(); try { // ONLY required if NOT using chnage tracking enabled Context.ChangeTracker.DetectChanges(); } catch (Exception ex) { .... custom error tool return opResult; } return opResult; }