C# 正在更新EF6中的实体子集合

C# 正在更新EF6中的实体子集合,c#,entity-framework,entity-framework-6,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.

我有一个名为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();
    }
}
这将导致“违反多重性约束”。关系“comp1690模型驱动程序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; }
}
驾驶员资格模型:

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并再次尝试包含这些时,您将看到多重性错误

我不完全确定这是否能解决您的问题,因为我以前从未尝试过这种方法,但我很好奇,如果可以解决您的问题,您是否要循环查看资格列表并在上下文中将其状态手动设置为“删除”

因此,不是:

d.DriverQualifications.Clear();
在foreach循环中执行此操作:

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

再一次…不能保证这会起作用,但我认为您必须这样做,才能在初始get请求期间处理附加到上下文的实体。

在处理EF和引用DriverQualification->Qualification时使用引用,而不是FKs。事实上,我通常建议不要将FK添加到实体中,而是在实体配置中使用阴影属性EF Core或.Map,以避免访问它们。您面临的问题是EF仍在跟踪引用特定资格的DriverQualification实体,因此将资格设置为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


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

感谢您的输入,但设置entitystate时也会出现相同的错误,虽然您的代码将整个驱动程序对象设置为删除状态,而不仅仅是列表,但您是对的……当我对其进行伪编码时,并没有真正考虑到这一部分。我会看看是否可以重新创建。很抱歉,回复太晚了,我会马上尝试,只是关于使用实例而不是Fk的第一点,我使用了Fk,因为这些对象已经存在,我不想在创建DriverQualification时复制它们,基本上,DriverQualification指向已经存在的限定,如果我在DriverQualification->Qualification on insertion中有一个对象,它将在数据库中复制。如果您将上下文中的限定与DriverQualification关联,EF将更新适用的FK。如果您关联了一个新的或上下文不知道的限定实例,则会得到重复项,上下文会将其视为一个新实例,这可能会在重复密钥场景中运行。