entity framework以错误的顺序插入子对象

为什么EF首先在它所依赖的对象(TimesheetActivity)之前插入具有依赖项的子对象(PersonnelWorkRecord)。 还有什么我可以选择纠正这个?

ERD(简化)

这是由我直接控制的另一个系统预定义的。 ERD2

EF设置和保存代码

我不确定我理解为什么/如何entity framework按照它的顺序插入我拥有的对象但是这里是我用来插入父和几个子代的代码。

using (var db = new DataContext(user)) { timesheet.State = State.Added; timesheet.SequenceNumber = newSequenceNumber; this.PrepareAuditFields(timesheet); //To stop EF from trying to add all child objects remove them from the timehseets object. timesheet = RemoveChildObjects(timesheet, db); //Add the Timesheet object to the database context, and save. db.Timesheets.Add(timesheet); result = db.SaveChanges() > 0; } 

EF插入的SQL跟踪

当我运行代码时,我在PersonnelWorkRecord(TimesheetActivityID)上遇到SQL外键冲突,因为我还没有添加Activity(请参阅trace)。

 exec sp_executesql N'insert [dbo].[Timesheets]([ProjectID], [TimesheetStatusID], ... exec sp_executesql N'insert [dbo].[PersonnelWorkdays]([TimesheetID], [PersonnelID], ... exec sp_executesql N'insert [dbo].[PersonnelWorkRecords]([PersonnelWorkdayID],[TimesheetActivityID], ... 

数据上下文摘要

 modelBuilder.Entity().HasRequired(pwd => pwd.Personnel).WithMany(p => p.PersonnelWorkdays).HasForeignKey(pwd => pwd.PersonnelID).WillCascadeOnDelete(false); modelBuilder.Entity().HasRequired(pwd => pwd.Timesheet).WithMany(t => t.PersonnelWorkdays).HasForeignKey(pwd => pwd.TimesheetID).WillCascadeOnDelete(false); modelBuilder.Entity().HasRequired(pwr => pwr.PersonnelWorkday).WithMany(pwd => pwd.PersonnelWorkRecords).HasForeignKey(pwr => pwr.PersonnelWorkdayID).WillCascadeOnDelete(false); modelBuilder.Entity().HasRequired(pwr => pwr.TimesheetActivity).WithMany(ta => ta.PersonnelWorkRecords).HasForeignKey(pwr => pwr.TimesheetActivityID).WillCascadeOnDelete(false); modelBuilder.Entity().HasRequired(ta => ta.ProjectActivity).WithMany(a => a.TimesheetActivities).HasForeignKey(ta => ta.ProjectActivityCodeID).WillCascadeOnDelete(false); modelBuilder.Entity().HasOptional(ta => ta.Facility).WithMany(f => f.TimesheetActivities).HasForeignKey(tf => tf.FacilityID).WillCascadeOnDelete(false); modelBuilder.Entity().HasRequired(ta => ta.Timesheet).WithMany(t => t.TimesheetActivities).HasForeignKey(ta => ta.TimesheetID).WillCascadeOnDelete(false); 

删除子对象

这是子对象方法的代码。 我添加了此方法以从时间表的子对象相关对象中删除非外键的对象。 例如,我有一个Crew对象,但我也有一个CrewID外键,所以我设置了Crew = null,以便EF不会尝试插入它,因为它已经存在。

 private Timesheet RemoveChildObjects(Timesheet timesheet, DataContext db) { timesheet.Crew = null; timesheet.Foreman = null; timesheet.Location = null; timesheet.Project = null; timesheet.SigningProjectManager = null; timesheet.TimesheetStatus = null; timesheet.Creator = null; timesheet.Modifier = null; if (timesheet.TimesheetActivities != null) { foreach (TimesheetActivity tsa in timesheet.TimesheetActivities) { tsa.Creator = null; if (tsa.EquipmentWorkRecords != null) { tsa.EquipmentWorkRecords = RemoveChildObjects(tsa.EquipmentWorkRecords, db); } tsa.Facility = null; tsa.Modifier = null; if (tsa.PersonnelWorkRecords != null) { tsa.PersonnelWorkRecords = RemoveChildObjects(tsa.PersonnelWorkRecords, db); } tsa.ProjectActivity = null; tsa.Structures = null; tsa.Timesheet = null; } } if (timesheet.TimesheetEquipment != null) { foreach (TimesheetEquipment te in timesheet.TimesheetEquipment) { te.Equipment = null; te.Timesheet = null; } } if (timesheet.EquipmentWorkdays != null) { timesheet.EquipmentWorkdays = RemoveChildObjects(timesheet.EquipmentWorkdays, true, db); } if (timesheet.TimesheetPersonnel != null) { foreach (TimesheetPersonnel tp in timesheet.TimesheetPersonnel) { tp.Personnel = null; tp.PersonnelWorkday = null; if (tp.PersonnelWorkday != null) { tp.PersonnelWorkday = RemoveChildObjects(tp.PersonnelWorkday, db); } tp.Timesheet = null; } } if (timesheet.PersonnelWorkdays != null) { timesheet.PersonnelWorkdays = RemoveChildObjects(timesheet.PersonnelWorkdays, true, db); } return timesheet; } 

在EF保存之前调试值

根据我的理解,当调用dbContext.Save()时,将添加/修改/删除任何dbContex.ObjectNameHere.Local。 (取决于实体State的设置也是如此。)以下是我在调用save()并获得SQL FKexception之前尝试保存的内容。 步骤1第2步 然后我得到了FKexception。

INSERT语句与FOREIGN KEY约束“FK_PersonnelWorkRecords_TimesheetActivities”冲突。 冲突发生在数据库“VPMTEST_GC”,表“dbo.TimesheetActivities”,列’TimesheetActivityID’中。 该语句已终止。

笔记

如果有什么我可以发布以帮助描述我的问题,请告诉我。 我已经浏览了google / SO以获得答案,但到目前为止还没有可靠的答案,看起来EF无法确定插入对象的顺序,除非Domain模型设置不同? 我无法更改大多数对象的结构,因为它们被另一个系统使用。 我可以尝试更改我的EF调用,我不想使用Raw SQL,因为对象比我在这里发布的简化版本要广泛得多。

类似问题: 自引用实体和插入顺序

RemoveChildObjects方法中,我看到了这一行……

 tsa.Timesheet = null; 

因此,显然您将Timesheet.TimesheetActivities的逆导航属性设置为null 。 您是否对PersonnelWorkRecord.TimesheetActivityPersonnelWorkRecord.PersonnelWorkday执行相同的操作,即您是否在嵌套的RemoveChildObjects方法中将这些属性设置为null

这可能是一个问题,因为从TimesheetPersonnelWorkRecord有两条不同的路径,即:

 Timesheet -> TimesheetActivities -> PersonnelWorkRecords Timesheet -> PersonnelWorkdays -> PersonnelWorkRecords 

当你调用db.Timesheets.Add(timesheet)我相信EF将逐个遍历对象图中的每个分支,并在路径上确定哪些相关对象(“节点”)是依赖的,哪些是关系中的主体以确定插入顺序。 timesheet本身是其所有关系的主体,因此很明显必须先插入它。 然后,EF开始迭代其中一个集合Timesheet.TimesheetActivitiesTimesheet.PersonnelWorkdays 。 首先出现哪个并不重要。 EF显然是从Timesheet.PersonnelWorkdays开始的。 (如果从Timesheet.TimesheetActivities开始,它将无法解决问题,您将获得相同的exception,但使用PersonnelWorkRecord.PersonnelWorkday而不是PersonnelWorkRecord.TimesheetActivity 。) PersonnelWorkday仅依赖于已插入的Timesheet 。 因此,也可以插入PersonnelWorkday

然后EF继续遍历PersonnelWorkday.PersonnelWorkRecords 。 关于PersonnelWorkday依赖关系,再没有问题,因为之前已经插入了PersonnelWorkday 。 但是,当EF遇到PersonnelWorkRecordTimesheetActivity依赖关系时,它将看到此TimesheetActivitynull (因为您已将其设置为null )。 现在假设依赖性仅由外键属性TimesheetActivityID描述,它必须引用现有记录。 它插入PersonnelWorkRecord ,这违反了外键约束。

如果PersonnelWorkRecord.TimesheetActivity不为null EF将检测到此对象尚未插入,但它是PersonnelWorkRecord的主体。 因此,它可以确定必须在PersonnelWorkRecord 之前插入此TimesheetActivity

如果您不将反向导航属性设置为null – 或者至少不是PersonnelWorkRecord的两个导航属性,我希望您的代码可以正常工作。 (将其他导航属性(如tsa.Creatortsa.Facility等)设置为null应该不是问题,因为这些相关对象确实已存在于数据库中,并且您已为这些属性设置了正确的FK属性值。)

这可能不再有效,但是它是一个使用事务并单独添加每个子对象的选项吗?

注意:我认为Slauma的解决方案更完整,但是对于有类似问题的其他人来说,交易电话可能仍然是一个选择。