为什么EF首先在它所依赖的对象(TimesheetActivity)之前插入具有依赖项的子对象(PersonnelWorkRecord)。还有什么我可以选择纠正这个问题?
这是由我直接控制的另一个系统预定义的。
我不确定我理解为什么/如何实体框架按照它的顺序插入我拥有的对象,但这里是我用来插入父母和几个孩子的代码。
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;
}
当我运行代码时,我在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<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;
}
根据我的理解,在调用dbContext.Save()时,将添加/修改/删除dbContex.ObjectNameHere.Local。 (取决于实体State的设置。)以下是我在调用save()并获得SQL FK异常之前尝试保存的内容。 然后我得到了FK例外。
INSERT语句与FOREIGN KEY约束冲突 “FK_PersonnelWorkRecords_TimesheetActivities”。冲突发生了 在数据库“VPMTEST_GC”中,表“dbo.TimesheetActivities”,列 'TimesheetActivityID'。声明已经终止。
如果有什么我可以发布以帮助描述我的问题,请告诉我。我已经浏览了google / SO以获得答案,但到目前为止还没有可靠的答案,看起来EF无法确定插入对象的顺序,除非Domain模型设置不同?我无法更改大多数对象的结构,因为它们被另一个系统使用。我可以尝试更改我的EF调用,我不想使用Raw SQL,因为对象比我在这里发布的简化版本要广泛得多。
答案 0 :(得分:2)
在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
遍历。关于PersonnelWorkday
的{{1}}依赖关系,再次没有问题,因为之前已经插入了PersonnelWorkRecord
。但是,当EF遇到PersonnelWorkday
的{{1}}依赖关系时,它会看到此TimesheetActivity
为PersonnelWorkRecord
(因为您已将其设置为TimesheetActivity
)。现在假设依赖性仅由外键属性null
描述,它必须引用现有记录。它插入null
,这违反了外键约束。
如果TimesheetActivityID
不是PersonnelWorkRecord
,EF会检测到此对象尚未插入,但它是PersonnelWorkRecord.TimesheetActivity
的主体。因此,它可以确定必须在 null
之前插入<{1}}。
如果您未将反向导航属性设置为PersonnelWorkRecord
,或者至少不是TimesheetActivity
中的两个导航属性,我希望您的代码有效。 (将PersonnelWorkRecord
,null
等其他导航属性设置为PersonnelWorkRecord
应该不是问题,因为这些相关对象确实已存在于数据库中并且您已设置了正确的FK属性那些值。)
答案 1 :(得分:0)
这可能不再有效,但它是否可以选择使用事务并单独添加每个子对象?
注意:强> 我认为Slauma的解决方案更加完善,但是对于有类似问题的其他人来说,交易电话可能仍然是一种选择。