繁体   English   中英

实体框架以错误的顺序插入子对象

[英]Entity Framework inserting child objects in the wrong order

为什么EF首先在依赖对象的对象(TimesheetActivity)之前插入具有依赖项的子对象(PersonnelWorkRecord)。 另外,在纠正此问题上我有什么选择?

ERD(简体)

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

EF设置并保存代码

我不确定我为什么/如何按照实体框架的顺序插入对象,但是这是我用来插入一个父对象和几个孩子的代码。

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跟踪

当我运行代码时,由于尚未添加Activity(请参见跟踪),因此在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。 (这也取决于实体状态的设置。)这是EF在我调用save()并获取SQL FK异常之前试图保存的内容。 步骤1第2步 然后我得到FK例外。

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

笔记

请让我知道我是否可以发布任何内容来帮助描述我的问题。 我已经在google / so周围寻找了答案,但到目前为止还没有可靠的答案,除非域模型设置不同,否则EF无法确定插入对象的顺序? 我无法更改大多数对象被其他系统使用的结构。 我可以尝试更改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开头,则不会解决该问题,但是会出现相同的异常,但使用PersonnelWorkRecord.PersonnelWorkday而不是PersonnelWorkRecord.TimesheetActivity 。) PersonnelWorkday仅取决于已插入的Timesheet 因此,也可以插入PersonnelWorkday

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

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

我希望如果您未将逆向导航属性设置为null ,或者至少没有将PersonnelWorkRecord的两个导航属性设置为null ,那么您的代码将起作用。 (将其他导航属性(例如tsa.Creatortsa.Facility等)设置为null应该不会有问题,因为那些相关对象实际上已经存在于数据库中,并且您已经为这些对象设置了正确的FK属性值。)

这可能不再有效,但是可以选择使用事务并分别添加每个子对象吗?

注意:我认为Slauma的解决方案更加完善,但是对于其他遇到类似问题的人,仍然可以选择使用交易电话。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM