简体   繁体   English

在EF6中更新实体子集合

[英]Updating entity child collection in EF6

I have a model called Driver which contains a list of 'DriverQualifications' and on update I would want to add/remove/update values of current DriverQualifications. 我有一个名为Driver的模型,其中包含“ DriverQualifications”列表,并且在更新时我想添加/删除/更新当前DriverQualifications的值。

My current attempt to update by first clearing the list and readding all elements: 我当前的更新尝试是先清除列表并读取所有元素:

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

This results in 'Multiplicity constraint violated. 这导致违反了“多重性约束”。 The role 'Drivers' of the relationship 'COMP1690Model.DriverQualifications_ibfk_1' has multiplicity 1 or 0..1.' 关系'COMP1690Model.DriverQualifications_ibfk_1'的角色'Drivers'具有多重性1或0..1.。

How I'm adding values to the DB: 我如何向数据库添加值:

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

Driver model: 驱动器型号:

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 model: 驾驶员资格模型:

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

pretty sure your problem is related to the way the EF context loads/tracks entities. 非常确定您的问题与EF上下文加载/跟踪实体的方式有关。

This code: 这段代码:

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

does the following: 执行以下操作:

1) Loads the driver and all it's driver qualifications (and each of their qualifications). 1)加载驾驶员及其所有驾驶员资格(及其所有资格)。

2) Clears the qualifications from the current driver. 2)清除当前驱动程序的资格。

3) Loops through the incoming qualifications 3)循环进入的资格

4) Adds the "new" qualification to the current driver. 4)向当前驱动程序添加“新”资格。

I believe the issue is related to #2 and #4. 我认为问题与#2和#4有关。 Even through you "Clear" the qualifications, they are still being references by the EF context. 即使通过“清除”资格,EF上下文仍会引用这些资格。 When you get to #4 and attempt to include those again, you get the multiplicity error that you are seeing. 当您到达#4并尝试再次包含这些内容时,您会看到所看到的多重性错误。

I'm not entirely sure this will solve your issue as I've never tried this approach before, but I'm curious if you were to loop through the list of qualifications and manually set it's state in the context to deleted if that would resolve your problem. 我并不完全确定这会解决您的问题,因为我之前从未尝试过这种方法,但是我很好奇您是否要遍历资格列表并手动将上下文中的状态设置为Delete(如果可以解决)你的问题。

So, instead of: 因此,代替:

d.DriverQualifications.Clear();

Do this instead (inside a foreach loop): 改为这样做(在foreach循环内):

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

Again...can't guarantee this will work, but I think you are going to have to so something of this nature to deal with the entities that are attached to the context during your initial get request. 再次……不能保证这会起作用,但是我认为您将必须具有这种性质,以便在最初的get请求期间处理附加到上下文的实体。

When dealing with EF and references (DriverQualification -> Qualification) use the references, not the FKs. 处理EF和引用(DriverQualification-> Qualification)时,请使用引用,而不要使用FK。 In fact, I generally advise not to even add FKs to the entities, but rather use shadow properties (EF Core) or .Map() in the entity configuration to avoid having them accessible. 实际上,我通常建议甚至不要将FK添加到实体,而应在实体配置中使用阴影属性(EF Core)或.Map()以避免它们可访问。 The issue you are facing is that EF is still tracking DriverQualification entities that reference a particular Qualification, so setting a Qualification to null and updating a FK doesn't really work. 您面临的问题是EF仍在跟踪引用特定资格的DriverQualification实体,因此将Qualification设置为null并更新FK并不能真正起作用。

So you're passing back a Driver, want to load that driver entity fresh, and update their qualifications based on the passed in driver. 因此,您要传回驱动程序,想要重新加载该驱动程序实体,并根据传入的驱动程序更新其资格。

Assuming that the driver passed in came from a client (web app, etc.) and has been modified, we cannot "trust" it, or it's reference data, so it's good that you are loading it fresh rather than re-attaching it to the context. 假设传入的驱动程序来自客户端(Web应用程序等)并且已被修改,我们不能“信任”它或它的参考数据,因此最好是重新加载它,而不是重新将其附加到上下文。

edit: I recommend using a ViewModel rather than passing an entity, even if you don't intend to trust it. 编辑:我建议您使用ViewModel而不是传递实体,即使您不想信任它也是如此。 The main risk of passing the entity is that it can be tempting to re-attach/use it, or referenced entities when updating. 传递实体的主要风险是,在更新时,它可能很容易重新附加/使用它或引用的实体。 I had to second-check this answer because I thought I'd broken that rule when getting the updated Qualifications! 我不得不再次检查这个答案,因为我认为在获得更新的资格认证时我违反了该规则! :) Passing entity graphs to a client browser for instance also exposes more information about your domain than you should. :)例如,将实体图传递给客户端浏览器还会暴露比您应有的更多有关您域的信息。 Even if you don't display various columns/fks/reference data, clients can view this data using debugging tools. 即使您不显示各种列/ fks /参考数据,客户端也可以使用调试工具查看此数据。 It's also more data across the wire than may be needed. 通过网络传输的数据也可能超出所需。 Automapper can make transposing entities to view models a snap, and works with IQueryable as well. Automapper可以使转置实体轻松查看模型,并且还可以与IQueryable一起使用。 ( ProjectTo ). ProjectTo )。 /edit /编辑

I've renamed some of the variables for clarity.. (Ie val => updatedDriver) 为了清楚起见,我将一些变量重命名了。(即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();
}

This assumes that we want to remove qualification associations that are no longer associated to the driver, and add any new qualifications not already associated. 假设我们要删除不再与驱动程序关联的资格关联,并添加尚未关联的任何新资格。 (leaving any unchanged qualifications.) (保留任何不变的资格。)

The LamdaComparer you can find here 您可以在这里找到LamdaComparer

Basically, to avoid reference/key issues, stick with updating references and ignoring FKs entirely. 基本上,为避免引用/关键问题,请坚持更新引用并完全忽略FK。 For entities/contexts where I need to do a lot of "raw" updates I will declare just FKs and forgo adding references for performance. 对于需要进行大量“原始”更新的实体/上下文,我将仅声明FK,并放弃添加性能参考。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM