在EF6中更新实体子集合

时间:2019-03-06 20:37:34

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

我有一个名为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; }
}

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

2 个答案:

答案 0 :(得分: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并尝试再次包含这些内容时,您会看到所看到的多重性错误。

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

所以,而不是:

d.DriverQualifications.Clear();

执行此操作(在foreach循环内):

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

再次...不能保证这会起作用,但是我认为您必须具有这种性质,才能处理在初始获取请求期间附加到上下文的实体。

答案 1 :(得分:0)

在处理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();
}

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

您可以找到here的LamdaComparer

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