问题
为什么 EF 首先在它所依赖的对象 (TimesheetActivity) 之前插入一个具有依赖关系的子对象 (PersonnelWorkRecord)。另外,我有哪些纠正此问题的选择?
ERD(简体)
这是由另一个不受我直接控制的系统预定义的。
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 外键冲突,因为我还没有添加事件(请参阅跟踪)。
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<PersonnelWorkday>().HasRequired(pwd => pwd.Personnel).WithMany(p => p.PersonnelWorkdays).HasForeignKey(pwd => pwd.PersonnelID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkday>().HasRequired(pwd => pwd.Timesheet).WithMany(t => t.PersonnelWorkdays).HasForeignKey(pwd => pwd.TimesheetID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.PersonnelWorkday).WithMany(pwd => pwd.PersonnelWorkRecords).HasForeignKey(pwr => pwr.PersonnelWorkdayID).WillCascadeOnDelete(false);
modelBuilder.Entity<PersonnelWorkRecord>().HasRequired(pwr => pwr.TimesheetActivity).WithMany(ta => ta.PersonnelWorkRecords).HasForeignKey(pwr => pwr.TimesheetActivityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasRequired(ta => ta.ProjectActivity).WithMany(a => a.TimesheetActivities).HasForeignKey(ta => ta.ProjectActivityCodeID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().HasOptional(ta => ta.Facility).WithMany(f => f.TimesheetActivities).HasForeignKey(tf => tf.FacilityID).WillCascadeOnDelete(false);
modelBuilder.Entity<TimesheetActivity>().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。 (也取决于实体状态的设置。)这是在我调用 save() 并获得 SQL FK 异常之前 EF 试图保存的内容。 然后我得到 FK 异常。
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_PersonnelWorkRecords_TimesheetActivities". The conflict occurred in database "VPMTEST_GC", table "dbo.TimesheetActivities", column 'TimesheetActivityID'. The statement has been terminated.
注意事项
如果我可以发布任何内容来帮助描述我的问题,请告诉我。我在 google/SO 上四处寻找答案,但到目前为止还没有可靠的答案,看起来 EF 无法确定插入对象的顺序,除非域模型的设置不同?我无法更改大多数对象的结构,因为它们被另一个系统使用。我可以尝试更改我的 EF 调用,我宁愿不使用原始 SQL,因为对象比我在此处发布的简化版本要广泛得多。
最佳答案
在您的 RemoveChildObjects
方法中,我看到了一行...
tsa.Timesheet = null;
显然,您正在将 Timesheet.TimesheetActivities
的反向导航属性设置为 null
。您是否对 PersonnelWorkRecord.TimesheetActivity
和 PersonnelWorkRecord.PersonnelWorkday
执行相同的操作,即您是否也在嵌套的 中将这些属性设置为
方法?null
>RemoveChildObjects
这可能是个问题,因为从 Timesheet
到 PersonnelWorkRecord
有两条不同的路径,即:
Timesheet -> TimesheetActivities -> PersonnelWorkRecords
Timesheet -> PersonnelWorkdays -> PersonnelWorkRecords
当您调用 db.Timesheets.Add(timesheet)
时,我相信 EF 将逐个遍历对象图中的每个分支,并确定路径上哪些相关对象(“节点”)是相关的并且在确定插入顺序的关系中是主要的。 timesheet
本身是其所有关系的主体,因此很明显必须首先插入它。然后 EF 开始循环访问集合 Timesheet.TimesheetActivities
或 Timesheet.PersonnelWorkdays
之一。谁先到并不重要。显然 EF 以 Timesheet.PersonnelWorkdays
开头。 (如果它以 Timesheet.TimesheetActivities
开头,则不会解决问题,您会得到相同的异常,但使用 PersonnelWorkRecord.PersonnelWorkday
而不是 PersonnelWorkRecord.TimesheetActivity
。)PersonnelWorkday
仅依赖于已插入的 Timesheet
。因此,也可以插入 PersonnelWorkday
。
然后 EF 继续遍历 PersonnelWorkday.PersonnelWorkRecords
。关于 PersonnelWorkRecord
的 PersonnelWorkday
依赖项,同样没有问题,因为之前已经插入了 PersonnelWorkday
。但是,当 EF 遇到 PersonnelWorkRecord
的 TimesheetActivity
依赖项时,它将看到此 TimesheetActivity
为 null
(因为您已经将其设置为 null
)。现在假设依赖项由外键属性 TimesheetActivityID
单独描述,它必须引用现有记录。它插入 PersonnelWorkRecord
,这违反了外键约束。
如果 PersonnelWorkRecord.TimesheetActivity
不是 null
EF 将检测到此对象尚未插入,但它是 PersonnelWorkRecord
的主体.因此,它可以确定此 TimesheetActivity
必须插入 before PersonnelWorkRecord
。
如果您不将反向导航属性设置为null
,我希望您的代码能正常工作——或者至少不是PersonnelWorkRecord 中的两个导航属性
。 (将其他导航属性(如 tsa.Creator
、tsa.Facility
等设置为 null
应该不是问题,因为那些相关对象确实已经存在于数据库中,并且您已经为它们设置了正确的 FK 属性值。)
关于c# - Entity Framework 以错误的顺序插入子对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21869330/