簡體   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