简体   繁体   English

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

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

Question

Why is EF first inserting a child object (PersonnelWorkRecord) with a dependency, before the object that it is depended on (TimesheetActivity). 为什么EF首先在依赖对象的对象(TimesheetActivity)之前插入具有依赖项的子对象(PersonnelWorkRecord)。 Also what are my options on correcting this? 另外,在纠正此问题上我有什么选择?

ERD (simplified) ERD(简体)

This is predefined by another system out of my direct control. 这是我无法直接控制的另一个系统预定义的。 ERD2

EF setup and save code EF设置并保存代码

I am not sure I understand why/how Entity Framework is inserting the objects I have in the order it does however here is the code I am using to insert a parent and several children. 我不确定我为什么/如何按照实体框架的顺序插入对象,但是这是我用来插入一个父对象和几个孩子的代码。

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;
}

SQL Trace of EF's Inserts EF插入的SQL跟踪

When I run the code I get a SQL foreign key violation on the PersonnelWorkRecord (TimesheetActivityID) because I have not yet added the Activity (see trace). 当我运行代码时,由于尚未添加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], ...

Data Context Summary 数据上下文摘要

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);

Remove Child Objects 删除子对象

Here is the code for the child objects method. 这是子对象方法的代码。 I added this method to remove the objects from the timesheets' child objects related objects that are not foreign keys. 我添加了此方法,以从与非外键的时间表的子对象相关的对象中删除这些对象。 For example I have a Crew object but I also have a CrewID foreign key, so I have set Crew = null so that EF does not try to insert it since it already exists. 例如,我有一个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;
    }

Debug of values before EF save 在EF保存之前调试值

From my understanding anything an dbContex.ObjectNameHere.Local will be added/modified/deleted when a dbContext.Save() is called. 据我了解,当调用dbContext.Save()时,将添加/修改/删除dbContex.ObjectNameHere.Local。 (Depending on what the entity State is set too.) Here is what EF is trying to save before I call the save() and get an SQL FK exception. (这也取决于实体状态的设置。)这是EF在我调用save()并获取SQL FK异常之前试图保存的内容。 步骤1第2步 Then I get the FK exception. 然后我得到FK例外。

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_PersonnelWorkRecords_TimesheetActivities". INSERT语句与FOREIGN KEY约束“ FK_PersonnelWorkRecords_TimesheetActivities”冲突。 The conflict occurred in database "VPMTEST_GC", table "dbo.TimesheetActivities", column 'TimesheetActivityID'. 数据库“ VPMTEST_GC”的表“ dbo.TimesheetActivities”的列“ TimesheetActivityID”中发生了冲突。 The statement has been terminated. 该语句已终止。

Notes 笔记

Please let me know if there is anything I can post to help describe my question. 请让我知道我是否可以发布任何内容来帮助描述我的问题。 I have looked around google / SO for answers but so far no solid answers, it looks like EF can not determine the order of inserting objects unless the Domain model is setup differently? 我已经在google / so周围寻找了答案,但到目前为止还没有可靠的答案,除非域模型设置不同,否则EF无法确定插入对象的顺序? I am not able to change the structure of most objects as they are used by another system. 我无法更改大多数对象被其他系统使用的结构。 I can attempt to change my EF call, I would prefer not to use Raw SQL as the objects are quite a bit more extensive then the simplified versions I have posted here. 我可以尝试更改EF调用,我不希望不使用Raw SQL,因为对象比我在此处发布的简化版本要广泛得多。

Similar questions: Self referencing entity and insert order 类似的问题: 自引用实体和插入顺序

In your RemoveChildObjects method I see the line... 在您的RemoveChildObjects方法中,我看到了...

tsa.Timesheet = null;

So, apparently your are setting the inverse navigation property of Timesheet.TimesheetActivities to null . 因此,显然您正在将Timesheet.TimesheetActivities的反向导航属性设置为null Are you doing the same with PersonnelWorkRecord.TimesheetActivity and PersonnelWorkRecord.PersonnelWorkday , ie do you set those properties to null as well in the nested RemoveChildObjects methods? 您是否对PersonnelWorkRecord.TimesheetActivityPersonnelWorkRecord.PersonnelWorkday做相同的操作,即是否在嵌套的RemoveChildObjects方法中将这些属性设置为null

This could be a problem because you have two different paths from Timesheet to PersonnelWorkRecord , namely: 这可能是一个问题,因为从TimesheetPersonnelWorkRecord有两条不同的路径,即:

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

When you call db.Timesheets.Add(timesheet) I believe EF will traverse each branch in the object graph one by one and determine on the path which related objects ("nodes") are dependent and which are principal in a relationship to determine the order of insertion. 我相信当您调用db.Timesheets.Add(timesheet)我相信EF会一次遍历对象图中的每个分支,并在路径上确定哪些相关对象(“节点”)是依赖的,哪些是关系中的主要对象,以确定插入顺序。 timesheet itself is principal for all its relationships, therefore it is clear that it must be inserted first. timesheet本身是所有关系的主体,因此很显然必须首先插入它。 Then EF starts to iterate through one of the collections Timesheet.TimesheetActivities or Timesheet.PersonnelWorkdays . 然后,EF开始遍历集合Timesheet.TimesheetActivitiesTimesheet.PersonnelWorkdays Which one comes first doesn't matter. 哪个先出现都没关系。 Apparently EF starts with Timesheet.PersonnelWorkdays . 显然,EF以Timesheet.PersonnelWorkdays开头。 (It would not solve the problem if it would start with Timesheet.TimesheetActivities , you would get the same exception, but with PersonnelWorkRecord.PersonnelWorkday instead of PersonnelWorkRecord.TimesheetActivity .) PersonnelWorkday is only dependent on Timesheet which is already inserted. (如果以Timesheet.TimesheetActivities开头,则不会解决该问题,但是会出现相同的异常,但使用PersonnelWorkRecord.PersonnelWorkday而不是PersonnelWorkRecord.TimesheetActivity 。) PersonnelWorkday仅取决于已插入的Timesheet So, PersonnelWorkday can be inserted as well. 因此,也可以插入PersonnelWorkday

Then EF continues traversing with PersonnelWorkday.PersonnelWorkRecords . 然后,EF继续使用PersonnelWorkday.PersonnelWorkRecords遍历。 With respect to the PersonnelWorkday dependency of PersonnelWorkRecord there is again no problem because the PersonnelWorkday has already been inserted before. 关于PersonnelWorkday的依赖PersonnelWorkRecord再有,因为没有问题PersonnelWorkday之前已经插入。 But when EF encounters the TimesheetActivity dependency of PersonnelWorkRecord it will see that this TimesheetActivity is null (because you've set it to null ). 但是,当EF遇到PersonnelWorkRecordTimesheetActivity依赖关系时,它将看到此TimesheetActivitynull (因为您已将其设置为null )。 It assumes now that the dependency is described by the foreign key property TimesheetActivityID alone which must refer to an existing record. 现在假定依赖项仅由外键属性TimesheetActivityID来描述,该属性必须引用现有记录。 It inserts the PersonnelWorkRecord and this violates a foreign key constraint. 它插入PersonnelWorkRecord ,这违反了外键约束。

If PersonnelWorkRecord.TimesheetActivity is not null EF would detect that this object hasn't been inserted yet but it is the principal for PersonnelWorkRecord . 如果PersonnelWorkRecord.TimesheetActivity不为null EF将检测到尚未插入该对象,但这是PersonnelWorkRecord的主体。 So, it can determine that this TimesheetActivity must be inserted before the PersonnelWorkRecord . 因此,它可以确定必须在PersonnelWorkRecord 之前插入此TimesheetActivity

I would hope that your code works if you don't set the inverse navigation properties to null - or at least not the two navigation properties in PersonnelWorkRecord . 我希望如果您未将逆向导航属性设置为null ,或者至少没有将PersonnelWorkRecord的两个导航属性设置为null ,那么您的代码将起作用。 (Setting the other navigation properties like tsa.Creator , tsa.Facility , etc. to null should not be a problem because those related objects really already exist in the database and you have set the correct FK property values for those.) (将其他导航属性(例如tsa.Creatortsa.Facility等)设置为null应该不会有问题,因为那些相关对象实际上已经存在于数据库中,并且您已经为这些对象设置了正确的FK属性值。)

This may no longer be valid, however is it an option to use a transaction and adding each child object individually? 这可能不再有效,但是可以选择使用事务并分别添加每个子对象吗?

Note: I think Slauma's solution is more complete, however a transaction call may still be an option for others with similar issues. 注意:我认为Slauma的解决方案更加完善,但是对于其他遇到类似问题的人,仍然可以选择使用交易电话。

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

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