当孩子有身份证时,如何使用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的目的…
试试这些:
- 使用
DbSet
的Create()
方法 - 将新实例添加到
Context
的Answers
集合中
您已为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; }