【问题标题】:Updating entity child collection in EF6在 EF6 中更新实体子集合
【发布时间】:2019-03-06 20:37:33
【问题描述】:

我有一个名为 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”的角色“驱动程序”具有多重性 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; }
}

DriverQualification 模型:

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

【问题讨论】:

    标签: c# entity-framework entity-framework-6


    【解决方案1】:

    很确定您的问题与 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 并尝试再次包含这些内容时,您会看到您所看到的多重性错误。

    我不完全确定这会解决您的问题,因为我以前从未尝试过这种方法,但我很好奇您是否要遍历资格列表并在上下文中手动将其状态设置为删除 if这将解决您的问题。

    所以,而不是:

    d.DriverQualifications.Clear();
    

    改为这样做(在 foreach 循环内):

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

    再次...不能保证这会奏效,但我认为您将不得不采取这种性质的措施来处理在初始获取请求期间附加到上下文的实体。

    【讨论】:

    • 感谢您的输入,虽然设置 entitystate 时会发生同样的错误,但您的代码会将整个驱动程序对象设置为已删除状态,而不仅仅是列表
    • 你是对的....当我对它进行伪编码时,并没有真正考虑到这部分。我看看能不能再造。
    【解决方案2】:

    在处理 EF 和引用(DriverQualification -> Qualification)时,使用引用,而不是 FK。事实上,我通常建议不要向实体添加 FK,而是在实体配置中使用影子属性(EF Core)或.Map() 以避免它们可访问。您面临的问题是 EF 仍在跟踪引用特定 Qualification 的 DriverQualification 实体,因此将 Qualification 设置为 null 并更新 FK 并不起作用。

    因此,您要传回一个驱动程序,想要重新加载该驱动程序实体,并根据传入的驱动程序更新其资格。

    假设传入的驱动程序来自客户端(网络应用程序等)并已被修改,我们不能“信任”它,或者它是参考数据,所以最好是重新加载它而不是重新加载它将其附加到上下文中。

    编辑:我建议使用 ViewModel 而不是传递实体,即使您不打算信任它。传递实体的主要风险是它可能很容易重新附加/使用它,或者在更新时引用实体。我不得不再次检查这个答案,因为我认为我在获得更新的资格时违反了该规则! :) 例如,将实体图传递给客户端浏览器也会比您应该公开更多有关您的域的信息。即使您不显示各种列/fks/reference 数据,客户端也可以使用调试工具查看这些数据。网络上的数据也比可能需要的多。 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();
    }
    

    这假设我们要删除不再与驱动程序关联的资格关联,并添加任何尚未关联的新资格。 (保留任何不变的资格。)

    你可以找到here的LamdaComparer

    基本上,为避免引用/密钥问题,请坚持更新引用并完全忽略 FK。对于我需要进行大量“原始”更新的实体/上下文,我将仅声明 FK 并放弃添加性能参考。

    【讨论】:

    • 抱歉回复晚了,一会儿再试,只是关于第一点使用实例而不是Fk,我使用了Fk,因为这些对象已经存在并且我不想当我创建 DriverQualification 时复制它们,基本上 DriverQualification 指向已经存在的 Qualification,如果我在 DriverQualification -> Qualification 中有一个对象,它将在数据库中复制它
    • 如果您将上下文中的 Qualification 与 DriverQualification 相关联,EF 将更新适用的 FK。出现重复的地方是,如果您关联一个新的或 Context 不知道的 Qualification 实例,则上下文会将其视为新实例,这可能会遇到重复键场景。
    猜你喜欢
    • 2014-08-31
    • 1970-01-01
    • 1970-01-01
    • 2018-05-05
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-05
    相关资源
    最近更新 更多