簡體   English   中英

在EF6中更新實體子集合

[英]Updating entity child collection in EF6

我有一個名為Driver的模型,其中包含“ DriverQualifications”列表,並且在更新時我想添加/刪除/更新當前DriverQualifications的值。

我當前的更新嘗試是先清除列表並讀取所有元素:

public void UpdateOne(Driver val)
{
    using (var db = new COMP1690Entities())
    {
        Driver d = db.Drivers.Where((dr) => dr.Id == val.Id).Include("DriverQualifications.Qualification").FirstOrDefault();
        d.DriverQualifications.Clear();
        foreach (DriverQualification q in val.DriverQualifications)
        {
            q.Fk_Qualifications_Id = q.Qualification.Id;
            q.Qualification = null;
            d.DriverQualifications.Add(q);
        }
        d.Phone_Number = val.Phone_Number;
        db.SaveChanges();
    }
}

這導致違反了“多重性約束”。 關系'COMP1690Model.DriverQualifications_ibfk_1'的角色'Drivers'具有多重性1或0..1.。

我如何向數據庫添加值:

    public void CreateOne(Driver val)
    {
        using (var db = new COMP1690Entities())
        {
            foreach(DriverQualification q in val.DriverQualifications)
            {
                q.Fk_Qualifications_Id = q.Qualification.Id;
                q.Qualification = null;
            }
            db.Drivers.Add(val);
            db.SaveChanges();
        }
    }

驅動器型號:

public partial class Driver
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Driver()
    {
        this.DriverQualifications = new HashSet<DriverQualification>();
        this.DriverTrainings = new HashSet<DriverTraining>();
    }

    public int Id { get; set; }
    public string Phone_Number { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<DriverQualification> DriverQualifications { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<DriverTraining> DriverTrainings { get; set; }
}

駕駛員資格模型:

public partial class DriverQualification
{
    public int Id { get; set; }
    public Nullable<System.DateTime> Expiry_Date { get; set; }
    public int Fk_Driver_Id { get; set; }
    public int Fk_Qualifications_Id { get; set; }

    public virtual Driver Driver { get; set; }
    public virtual Qualification Qualification { get; set; }
}

非常確定您的問題與EF上下文加載/跟蹤實體的方式有關。

這段代碼:

using (var db = new COMP1690Entities())
    {
        Driver d = db.Drivers.Where((dr) => dr.Id == val.Id).Include("DriverQualifications.Qualification").FirstOrDefault();
        d.DriverQualifications.Clear();
        foreach (DriverQualification q in val.DriverQualifications)
        {
            q.Fk_Qualifications_Id = q.Qualification.Id;
            q.Qualification = null;
            d.DriverQualifications.Add(q);
        }
        d.Phone_Number = val.Phone_Number;
        db.SaveChanges();
    }

執行以下操作:

1)加載駕駛員及其所有駕駛員資格(及其所有資格)。

2)清除當前驅動程序的資格。

3)循環進入的資格

4)向當前驅動程序添加“新”資格。

我認為問題與#2和#4有關。 即使通過“清除”資格,EF上下文仍會引用這些資格。 當您到達#4並嘗試再次包含這些內容時,您會看到所看到的多重性錯誤。

我並不完全確定這會解決您的問題,因為我之前從未嘗試過這種方法,但是我很好奇您是否要遍歷資格列表並手動將上下文中的狀態設置為Delete(如果可以解決)你的問題。

因此,代替:

d.DriverQualifications.Clear();

改為這樣做(在foreach循環內):

db.Entry(d).State = System.Data.Entity.EntityState.Deleted;

再次……不能保證這會起作用,但是我認為您將必須具有這種性質,以便在最初的get請求期間處理附加到上下文的實體。

處理EF和引用(DriverQualification-> Qualification)時,請使用引用,而不要使用FK。 實際上,我通常建議甚至不要將FK添加到實體,而應在實體配置中使用陰影屬性(EF Core)或.Map()以避免它們可訪問。 您面臨的問題是EF仍在跟蹤引用特定資格的DriverQualification實體,因此將Qualification設置為null並更新FK並不能真正起作用。

因此,您要傳回驅動程序,想要重新加載該驅動程序實體,並根據傳入的驅動程序更新其資格。

假設傳入的驅動程序來自客戶端(Web應用程序等)並且已被修改,我們不能“信任”它或它的參考數據,因此最好是重新加載它,而不是重新將其附加到上下文。

編輯:我建議您使用ViewModel而不是傳遞實體,即使您不想信任它也是如此。 傳遞實體的主要風險是,在更新時,它可能很容易重新附加/使用它或引用的實體。 我不得不再次檢查這個答案,因為我認為在獲得更新的資格認證時我違反了該規則! :)例如,將實體圖傳遞給客戶端瀏覽器還會暴露比您應有的更多有關您域的信息。 即使您不顯示各種列/ fks /參考數據,客戶端也可以使用調試工具查看此數據。 通過網絡傳輸的數據也可能超出所需。 Automapper可以使轉置實體輕松查看模型,並且還可以與IQueryable一起使用。 ProjectTo )。 /編輯

為了清楚起見,我將一些變量重命名了。(即val => updatedDriver)

using (var context = new COMP1690Entities())
{
    var updatedQualificationIds = updatedDriver.DriverQualifications.Select(dq => dq.Qualification.Id).ToList();
    // Get the updated qualification entities from the DB.
    var updatedQualifications = context.Qualifications.Where(q => updatedQualificationIds.Contains(q.Id)).ToList();

    var driver = context.Drivers.Where(d => d.Id == updatedDriver.Id)
        .Include(d => d.DriverQualifications)
        .Include("DriverQualifications.Qualification").Single();

    var driverQualificationsToRemove = driver.DriverQualifications
        .Where(dq => !updatedQualificationIds.Contains(udq.Qualification.Id));

    foreach(var driverQualification in driverQualificationsToRemove)
        driver.DriverQualifications.Remove(driverQualification);

    var driverQualificationsToAdd = updatedDriverQualifications
        .Except(driver.DriverQualifications.Select(dq => dq.Qualification),
            new LamdaComparer((q1,q2) => q1.Id == q2.Id))
        .Select(q => new DriverQualification { Qualification = q })
        .ToList();

    driver.DriverQualifications.AddRange(driverQualificationsToAdd);

    driver.PhoneNumber = updatedDriver.PhoneNumber;

    context.SaveChanges();
}

假設我們要刪除不再與驅動程序關聯的資格關聯,並添加尚未關聯的任何新資格。 (保留任何不變的資格。)

您可以在這里找到LamdaComparer

基本上,為避免引用/關鍵問題,請堅持更新引用並完全忽略FK。 對於需要進行大量“原始”更新的實體/上下文,我將僅聲明FK,並放棄添加性能參考。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM