Обновление дочерней коллекции сущности в EF6 - PullRequest
0 голосов
/ 06 марта 2019

У меня есть модель с именем 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; }
}

Модель квалификации водителя:

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 ]

1 голос
/ 07 марта 2019

уверен, что ваша проблема связана с тем, как контекст 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.

0 голосов
/ 07 марта 2019

При работе с EF и ссылками (DriverQualification -> Qualification) используйте ссылки, а не FK.Фактически, я вообще советую не добавлять FK к сущностям, а использовать теневые свойства (EF Core) или .Map() в конфигурации сущностей, чтобы избежать их доступности.Проблема, с которой вы сталкиваетесь, заключается в том, что EF по-прежнему отслеживает сущности DriverQualification, которые ссылаются на определенную квалификацию, поэтому установка нулевой квалификации и обновление FK на самом деле не работает.

Итак, вы возвращаете драйвер,хочу загрузить эту сущность драйвера заново и обновить их квалификацию на основе переданного драйвера.

Предполагая, что переданный драйвер поступил от клиента (веб-приложения и т. д.) и был изменен, мы не можем "доверять ему или ссылочным данным, поэтому хорошо, что вы загружаете его свежим, а не повторно присоединяете его к контексту.

edit: Я рекомендую использовать ViewModel, а не передаватьсущность, даже если вы не собираетесь доверять ей.Основной риск передачи сущности заключается в том, что может возникнуть соблазн повторно присоединить / использовать ее или ссылочные сущности при обновлении.Мне пришлось перепроверить этот ответ, потому что я думал, что нарушил это правило при получении обновленных квалификаций!:) Передача графов сущностей в клиентский браузер, например, также предоставляет больше информации о вашем домене, чем вы должны.Даже если вы не отображаете различные столбцы / fks / справочные данные, клиенты могут просматривать эти данные с помощью инструментов отладки.Это также больше данных по проводам, чем может потребоваться.Automapper может сделать транспонирование объектов для просмотра моделей одним щелчком и также работает с IQueryable.(ProjectTo). / edit

Я переименовал некоторые переменные для ясности .. (т.е. 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 и воздержусь от добавления ссылок на производительность.

...