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

栏目: 数据库 · 发布时间: 5年前

内容简介:翻译自:https://stackoverflow.com/questions/21869330/entity-framework-inserting-child-objects-in-the-wrong-order

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

ERD(简化)

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

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

当我运行代码时,我在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;
    }

在EF保存之前调试值

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

然后我得到了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无法确定插入对象的顺序,除非Domain模型设置不同?我无法更改大多数对象的结构,因为它们被另一个系统使用.我可以尝试更改我的EF调用,我不想使用Raw SQL,因为对象比我在这里发布的简化版本要广泛得多.

类似的问题: Self referencing entity and insert order

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

tsa.Timesheet = null;

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

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

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

当你调用db.Timesheets.Add(timesheet)时,我相信EF将逐个遍历对象图中的每个分支,并在路径上确定哪些相关对象(“节点”)是依赖的,哪些是关系中的主体以确定插入顺序.时间表本身是其所有关系的主体,因此很明显必须先插入它.然后,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的主体.因此,它可以确定必须在PersonnelWorkRecord之前插入此TimesheetActivity.

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

翻译自:https://stackoverflow.com/questions/21869330/entity-framework-inserting-child-objects-in-the-wrong-order


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员健康指南

程序员健康指南

Joe Kutner / 陈少芸 / 人民邮电出版社 / 2014-9-20 / 31.60元

本书是为程序员量身制作的健康指南,针对头痛、眼部疲劳、背部疼痛和手腕疼痛等常见的问题,简要介绍了其成因、测试方法,并列出了每天的行动计划,从运动、饮食等方面给出详细指导,帮助程序员在不改变工作方式的情况下轻松拥有健康。 本书适合程序员、长期伏案工作的其他人群以及所有关心健康的人士阅读。一起来看看 《程序员健康指南》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具