Отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не допускают значения NULL.

Я получаю эту ошибку, когда получаю GetById () для объекта, а затем устанавливаю коллекцию дочерних объектов в свой новый список, который поступает из представления MVC.

Не удалось выполнить операцию: связь не может быть изменена, поскольку одно или несколько свойств внешнего ключа не допускают значения NULL. Когда в отношение вносятся изменения, соответствующему свойству внешнего ключа присваивается значение NULL. Если внешний ключ не поддерживает нулевые значения, должна быть определена новая связь, свойству внешнего ключа должно быть присвоено другое ненулевое значение или несвязанный объект должен быть удален.

Я не совсем понимаю эту строчку:

Отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не допускают значения NULL.

Зачем мне менять отношения между двумя объектами? Он должен оставаться неизменным на протяжении всего срока службы всего приложения.

Код, в котором возникает исключение, - это простое присвоение измененных дочерних классов в коллекции существующему родительскому классу. Мы надеемся, что это позволит удалить дочерние классы, добавить новые и внести изменения. Я бы подумал, что Entity Framework справится с этим.

Строки кода можно разделить на:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

person jaffa    schedule 04.04.2011    source источник
comment
Я нашел свой ответ на покупку, используя решение № 2 в приведенной ниже статье, в основном я создал добавленный первичный ключ в дочернюю таблицу для ссылки на родительскую таблицу (так что у нее есть 2 первичных ключа (внешний ключ для родительской таблицы и идентификатор для дочерней таблицы). c-sharpcorner.com/UploadFile/ff2f08/   -  person yougotiger    schedule 02.10.2019
comment
@jaffa, я нашел свой ответ здесь stackoverflow.com/questions/22858491/   -  person antonio    schedule 11.11.2019


Ответы (20)


Вы должны удалить старые дочерние элементы thisParent.ChildItems один за другим вручную. Entity Framework не делает этого за вас. Наконец, он не может решить, что вы хотите делать со старыми дочерними элементами - хотите ли вы их выбросить или хотите сохранить и назначить их другим родительским объектам. Вы должны сообщить Entity Framework о своем решении. Но одно из этих двух решений вы ДОЛЖНЫ принять, поскольку дочерние сущности не могут существовать отдельно без ссылки на какой-либо родительский элемент в базе данных (из-за ограничения внешнего ключа). Это в основном то, что говорит исключение.

Изменить

Что бы я сделал, если бы можно было добавлять, обновлять и удалять дочерние элементы:

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

Примечание: это не проверено. Предполагается, что коллекция дочерних элементов имеет тип ICollection. (Обычно у меня IList, и тогда код выглядит немного иначе.) Я также убрал все абстракции репозитория, чтобы не усложнять.

Я не знаю, хорошее ли это решение, но я считаю, что необходимо проделать некоторую тяжелую работу в этом направлении, чтобы позаботиться обо всех видах изменений в коллекции навигации. Я также был бы рад увидеть более простой способ сделать это.

person Slauma    schedule 04.04.2011
comment
Так что, если изменить только некоторые? Означает ли это, что мне все еще нужно удалить их и добавить снова? - person jaffa; 04.04.2011
comment
@Jon: Нет, конечно, вы также можете обновить существующие элементы. Я добавил пример того, как я, вероятно, обновлю дочернюю коллекцию, см. Раздел «Правка» выше. - person Slauma; 04.04.2011
comment
@Slauma: Лол, если бы я знал, что вы собираетесь изменить свой ответ, я бы не стал писать свой ответ ... - person Ladislav Mrnka; 04.04.2011
comment
@ Ладислав: Нет, нет, я рад, что ты написал свой ответ. По крайней мере, теперь я знаю, что это не полная чушь и слишком сложное то, что я сделал выше. - person Slauma; 05.04.2011
comment
@Slauma: Я попробовал приведенный выше код, и он выглядит неплохо, однако я столкнулся с проблемой при удалении элементов из коллекции. Ошибка: Коллекция была изменена; операция перечисления может не выполняться. Есть идеи, как это обойти? - person jaffa; 05.04.2011
comment
@Jon: Попробуйте добавить ToList() после метода Where: ....Where(c => c.ID != 0).ToList(). - person Slauma; 05.04.2011
comment
@Jon: Я изменил код, чтобы добавить ToList(), и написал комментарий к образцу. Действительно, необходимо сделать копию с ToList(), потому что удаление из контекста удаляет также внутреннюю коллекцию. - person Slauma; 05.04.2011
comment
@Slauma: Да, я понял, что Remove () изменяет существующее перечисление, следовательно, либо используя ToList () в цикле for, либо используя отдельный 'var y = x.toList (), а затем используя y в for ()'. Спасибо еще раз! - person jaffa; 07.04.2011
comment
Поскольку вы выполняете вложенные циклы, производительность может стать проблемой, если набор данных для обновления достаточно велик. Альтернативный подход - отсортировать данные по ключу, а затем просмотреть два набора данных по одному элементу за раз, чтобы определить, нужно ли вам добавить, обновить или удалить элемент. - person Scott Mitchell; 27.06.2011
comment
Было бы проще удалить все дочерние элементы, а затем воссоздать их? Или это будет медленнее, чем вложенные циклы? - person SOfanatic; 22.04.2013
comment
@SOfanatic: в случаях, когда у вас есть другие сущности, ссылающиеся на дочернюю сущность, удалить ее будет невозможно. - person Slauma; 22.04.2013
comment
@Slauma: да, я имел в виду случай, когда между родителем и дочерними элементами существует связь «один ко многим», поэтому, если родитель удален, все дочерние элементы также должны быть удалены. - person SOfanatic; 22.04.2013
comment
Я бы добавил условие при получении originalChildItem в foreach: ... Where (c = ›c.ID == childItem.ID && c.ID! = 0), иначе он вернет только что добавленных дочерних элементов, если childItem.ID == 0. - person perfect_element; 01.03.2014
comment
@perfect_element: Вы абсолютно правы. Это была серьезная ошибка в коде почти трехлетней давности. Спасибо! - person Slauma; 01.03.2014
comment
Пытаюсь потратить эту работу впустую за неделю, спасибо за публикацию. EF нужно попытаться справиться с этим самостоятельно. - person JsonStatham; 09.02.2016
comment
@Slauma Вы знаете, как это сделать с помощью репозиториев? Это означает без прямого использования _dbContext. - person Sampath; 13.02.2016
comment
Для чего тогда нужна опция WillCascadeOnDelete ()? - person Dandy; 29.04.2016
comment
Я столкнулся с той же проблемой и тем же вопросом, что и @jaffa. Но из первого абзаца этого ответа становится более ясно, что пытается сказать EF. Спасибо за объяснение и ответ! - person Aamol; 27.07.2016

Причина, по которой вы столкнулись с этим, связана с разницей между составом и агрегированием.

В композиции дочерний объект создается при создании родительского объекта и уничтожается, когда его родительский объект уничтожается. Таким образом, его время жизни контролируется его родителем. например Сообщение в блоге и его комментарии. Если сообщение удалено, следует удалить его комментарии. Нет смысла оставлять комментарии к несуществующему посту. То же самое для заказов и элементов заказа.

В совокупности дочерний объект может существовать независимо от своего родителя. Если родительский объект уничтожен, дочерний объект все еще может существовать, так как позже он может быть добавлен к другому родительскому объекту. например: связь между плейлистом и песнями в этом плейлисте. Если плейлист удален, песни удалять не надо. Их можно добавить в другой список воспроизведения.

Entity Framework различает отношения агрегации и композиции следующим образом:

  • Для композиции: ожидается, что дочерний объект будет иметь составной первичный ключ (ParentID, ChildID). Это сделано намеренно, поскольку идентификаторы детей должны входить в компетенцию их родителей.

  • Для агрегации: ожидается, что свойство внешнего ключа в дочернем объекте будет иметь значение NULL.

Итак, причина, по которой у вас возникла эта проблема, заключается в том, как вы установили свой первичный ключ в своей дочерней таблице. Он должен быть составным, но это не так. Итак, Entity Framework рассматривает эту ассоциацию как агрегацию, что означает, что при удалении или очистке дочерних объектов дочерние записи не удаляются. Он просто удалит ассоциацию и установит для соответствующего столбца внешнего ключа значение NULL (чтобы эти дочерние записи позже могли быть связаны с другим родителем). Поскольку ваш столбец не допускает NULL, вы получаете указанное вами исключение.

Решения:

1- Если у вас есть веская причина не использовать составной ключ, вам необходимо удалить дочерние объекты явно. И это можно сделать проще, чем предложенные ранее решения:

context.Children.RemoveRange(parent.Children);

2- В противном случае, установив правильный первичный ключ в дочерней таблице, ваш код будет выглядеть более значимым:

parent.Children.Clear();
person Mosh    schedule 07.10.2015
comment
Я нашел это объяснение очень полезным. - person Booji Boy; 14.11.2015
comment
Хорошее объяснение состава и агрегации и того, как с этим связана структура сущностей. - person Chrysalis; 13.02.2016
comment
№1 было наименьшее количество кода, необходимого для решения проблемы. Спасибо! - person ryanulit; 15.03.2016
comment
На самом деле иногда использование составного ключа усложняет программу, и лучше иметь только один столбец идентификации. medium.com/@pablodalloglio/ - person Mohammad Reza; 17.02.2021

Это очень большая проблема. Что на самом деле происходит в вашем коде, так это:

  • Вы загружаете Parent из базы данных и получаете прикрепленную сущность
  • Вы заменяете его дочернюю коллекцию новой коллекцией отдельных дочерних элементов.
  • Вы сохраняете изменения, но во время этой операции все дочерние элементы считаются добавленными, поскольку EF не знала о них до этого времени. Таким образом, EF пытается установить значение null для внешнего ключа старых дочерних элементов и вставить все новые дочерние элементы => повторяющиеся строки.

Теперь решение действительно зависит от того, что вы хотите делать и как бы вы хотели это делать?

Если вы используете ASP.NET MVC, вы можете попробовать использовать UpdateModel или TryUpdateModel.

Если вы хотите просто обновить существующие дочерние элементы вручную, вы можете просто сделать что-то вроде:

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

Прикрепление на самом деле не требуется (установка состояния на Modified также присоединит объект), но мне это нравится, потому что это делает процесс более очевидным.

Если вы хотите изменить существующие, удалить существующие и вставить новые дочерние элементы, вы должны сделать что-то вроде:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();
person Ladislav Mrnka    schedule 04.04.2011
comment
Но есть ваше интересное замечание об использовании .Clone(). Вы имеете в виду, что ChildItem имеет другие дочерние навигационные свойства? Но в этом случае, разве мы не хотим, чтобы весь подграф был присоединен к контексту, поскольку мы ожидаем, что все дочерние элементы являются новыми объектами, если сам дочерний элемент является новым? (Ну, это может отличаться от модели к модели, но давайте предположим, что дочерние элементы зависят от дочернего элемента, как дочерние элементы зависят от родителя.) - person Slauma; 05.04.2011
comment
Вероятно, потребуется умный клон. - person Ladislav Mrnka; 05.04.2011
comment
Что делать, если вы не хотите, чтобы в вашем контексте была дочерняя коллекция? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie - person Kirsten Greed; 27.11.2013
comment
parent.ChildItems.Remove (дочерний); context.Childs.Remove (дочерний элемент); Это исправление двойного удаления может вызвать проблемы, СПАСИБО. Зачем нужны оба удаления? Почему удаления только из parent.ChildItems недостаточно, поскольку дочерние элементы живут только как дочерние элементы? - person Fernando Torres; 29.08.2017
comment
Спасибо за этот полезный код. моя проблема решена. - person Sedat Kumcu; 05.04.2021

Я нашел этот ответ гораздо более полезным для той же ошибки. Похоже, что EF не любит, когда вы удаляете, он предпочитает Удалить.

Вы можете удалить набор записей, прикрепленных к такой записи.

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

В этом примере для всех записей сведений, прикрепленных к заказу, установлено состояние «Удалить». (В процессе подготовки к добавлению обратно обновленных сведений в рамках обновления заказа)

person Greg Little    schedule 01.04.2014
comment
Я считаю, что это правильный ответ. - person desmati; 22.02.2018
comment
логичное и простое решение. - person sairfan; 06.11.2018

Понятия не имею, почему два других ответа так популярны!

Я считаю, что вы были правы, предполагая, что структура ORM должна справиться с этим - в конце концов, это то, что он обещает предоставить. В противном случае ваша модель предметной области будет повреждена из-за проблем с постоянством. NHibernate успешно справится с этим, если вы правильно настроите каскадные настройки. В Entity Framework это также возможно, они просто ожидают, что вы будете следовать лучшим стандартам при настройке модели базы данных, особенно когда им нужно сделать вывод о том, какое каскадирование должно выполняться:

Вам необходимо определить родительский - дочерние отношения правильно, используя "идентификация отношений".

Если вы это сделаете, Entity Framework знает, что дочерний объект идентифицирован родительским, и, следовательно, это должна быть ситуация «каскадное удаление-сиротство».

Помимо вышеуказанного, вам может потребоваться (из опыта NHibernate)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

вместо полной замены списка.

ОБНОВЛЕНИЕ

Комментарий @Slauma напомнил мне, что отдельные объекты - это еще одна часть общей проблемы. Чтобы решить эту проблему, вы можете воспользоваться подходом к использованию настраиваемого связывателя моделей, который создает ваши модели, пытаясь загрузить его из контекста. Это сообщение в блоге показывает пример того, что я имею в виду.

person Andre Luus    schedule 22.04.2013
comment
Настройка как идентифицирующая связь здесь не поможет, потому что сценарий в вопросе должен иметь дело с отдельными объектами (мой новый список, который поступает из представления MVC). Вам все равно нужно загрузить исходные дочерние элементы из БД, найти удаленные элементы в этой коллекции на основе отсоединенной коллекции, а затем удалить их из БД. Единственное отличие состоит в том, что с идентифицирующими отношениями вы можете вызывать parent.ChildItems.Remove вместо _dbContext.ChildItems.Remove. По-прежнему (EF ‹= 6) нет встроенной поддержки от EF, чтобы избежать длинного кода, такого как в других ответах. - person Slauma; 08.05.2013
comment
Я понимаю вашу точку зрения. Однако я считаю, что с настраиваемым связывателем модели, который загружает объект из контекста или возвращает новый экземпляр, описанный выше подход будет работать. Я обновлю свой ответ, чтобы предложить это решение. - person Andre Luus; 08.05.2013
comment
Да, вы можете использовать подшивку модели, но теперь вам нужно было сделать то, что было из других ответов в подшивке модели. Он просто перемещает проблему с уровня репо / сервиса на связыватель модели. По крайней мере, реального упрощения я не вижу. - person Slauma; 08.05.2013
comment
Упрощение - автоматическое удаление потерянных сущностей. Все, что вам нужно в подшивке модели, - это общий эквивалент return context.Items.Find(id) ?? new Item() - person Andre Luus; 08.05.2013
comment
Хорошая обратная связь для команды EF, но ваше предлагаемое решение, к сожалению, ничего не решает в области EF. - person Chris Moschini; 22.07.2014
comment
Как так? Я реализовал это и используется в производственном программном обеспечении. - person Andre Luus; 22.07.2014

Если вы используете AutoMapper с Entity Framework в одном классе, вы можете столкнуться с этой проблемой. Например, если ваш класс

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

Это попытается скопировать оба свойства. В этом случае ClassBId не имеет значения Null. Поскольку AutoMapper скопирует destination.ClassB = input.ClassB;, это вызовет проблему.

Установите AutoMapper на свойство Игнорировать ClassB.

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId
person jsgoupil    schedule 17.02.2016
comment
У меня аналогичная проблема с AutoMapper, но у меня это не работает :( См. stackoverflow.com/q/41430679/613605 - person J86; 03.01.2017

У меня была такая же проблема, но я знал, что в других случаях она работала нормально, поэтому я сократил проблему до следующего:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems имел составной первичный ключ (parentId + некоторый локальный столбец) и работал нормально.
  • ProblematicItems имеет собственный первичный ключ из одного столбца, а parentId только является FK. Это вызывало исключение после Clear ().

Все, что мне нужно было сделать, это сделать ParentId частью составного PK, чтобы указать, что дочерние элементы не могут существовать без родителя. Я использовал модель DB-first, добавил PK и пометил столбец parentId как EntityKey (поэтому мне пришлось обновить его как в DB, ​​так и в EF - не уверен, будет ли достаточно только EF).

«Я  А затем обновил модель EF И установил другое свойство как часть ключа сущности

Если подумать, это очень элегантное различие, которое EF использует, чтобы решить, «имеют ли дети смысл» без родителя (в этом случае Clear () не удалит их и не выдаст исключение, если вы не установите ParentId на что-то другое / специальное. ), или - как в исходном вопросе - мы ожидаем, что элементы будут удалены после того, как они будут удалены из родительского элемента.

person Ekus    schedule 31.03.2020
comment
+1 Отличный ответ, сегодня столкнулся с этой проблемой и никак не мог разобраться. Последовал за вашим решением (сделав столбец идентификатора и внешнего ключа составным ПК, и моя операция .Clear (), наконец, сработала. Спасибо. - person Dal; 21.01.2021
comment
Спасибо! Мучился 3 часа. Это самое короткое решение - person Alexander Brattsev; 26.01.2021

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

Однако в EF это не сработало, обнаружилась ошибка, описанная в этой ветке. Причина этого заключалась в том, что в моей модели данных объекта (файл edmx) свойства связи между родительской и дочерней таблицами были неправильными. Параметр End1 OnDelete был настроен как none ("End1" в моей модели - это конец, кратность которого равна 1).

Я вручную изменил параметр End1 OnDelete на Cascade, и это сработало. Я не знаю, почему EF не может это подобрать, когда я обновляю модель из базы данных (у меня есть первая модель базы данных).

Для полноты картины мой код для удаления выглядит так:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

Если бы у меня не было задано каскадное удаление, мне пришлось бы вручную удалить дочерние строки перед удалением родительской.

person Martin    schedule 05.08.2015

Это происходит потому, что дочерний объект помечен как измененный, а не удаленный.

И модификация, которую EF делает для дочерней сущности при выполнении parent.Remove(child), просто устанавливает ссылку на ее родительский объект на null.

Вы можете проверить дочерний EntityState, введя следующий код в Immediate Window Visual Studio, когда возникает исключение, после выполнения SaveChanges():

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

где X следует заменить на удаленный объект.

Если у вас нет доступа к ObjectContext для выполнения _context.ChildEntity.Remove(child), вы можете решить эту проблему, сделав внешний ключ частью первичного ключа дочерней таблицы.

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

Таким образом, если вы выполните parent.Remove(child), EF правильно пометит объект как удаленный.

person Mauricio Ramalho    schedule 25.08.2015

Этот тип решения помог мне:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

Важно сказать, что это удаляет все записи и вставляет их снова. Но для моего случая (меньше 10) это нормально.

Я надеюсь, что это помогает.

person Wagner Bertolini Junior    schedule 25.02.2014
comment
Выполняется ли повторная вставка с новыми идентификаторами или в первую очередь сохраняются идентификаторы ребенка, которые у них были? - person Pepito Fernandez; 06.05.2014

Сегодня я столкнулся с этой проблемой и хотел поделиться своим решением. В моем случае решение состояло в том, чтобы удалить дочерние элементы перед получением родительского элемента из базы данных.

Раньше я делал это так, как в приведенном ниже коде. Тогда я получу ту же ошибку, что и в этом вопросе.

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

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

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here
person Dino Bansigan    schedule 23.06.2015

Вы должны вручную очистить коллекцию ChildItems и добавить в нее новые элементы:

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

После этого вы можете вызвать метод расширения DeleteOrphans, который будет обрабатывать осиротевшие объекты (он должен вызываться между методами DetectChanges и SaveChanges).

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}
person Sat    schedule 22.09.2015
comment
У меня это сработало. Мне просто нужно было добавить context.DetectChanges();. - person Andy Edinborough; 24.12.2015

Я пробовал эти и многие другие решения, но ни одно из них не сработало. Поскольку это первый ответ в Google, я добавлю сюда свое решение.

Метод, который хорошо сработал для меня, заключался в том, чтобы исключить отношения из картины во время коммитов, чтобы EF не было ничего, что могло бы облажаться. Я сделал это, повторно найдя родительский объект в DBContext и удалив его. Поскольку все свойства навигации повторно найденного объекта равны нулю, дочерние отношения игнорируются во время фиксации.

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

Обратите внимание, что это предполагает, что внешние ключи настроены с помощью ON DELETE CASCADE, поэтому, когда родительская строка удаляется, дочерние элементы будут очищены базой данных.

person Steve    schedule 03.12.2013

Я использовал решение Моша, но мне не было очевидно, как сначала правильно реализовать ключ композиции в коде.

Итак, вот решение:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}
person PeterB    schedule 07.02.2017

Если вы используете Auto mapper и столкнулись с следующей проблемой, это хорошее решение, оно работает для меня.

https://www.codeproject.com/Articles/576393/Solutionplusto-aplus-Theplusoperationplusfailed

Поскольку проблема в том, что мы сопоставляем нулевые свойства навигации, и на самом деле нам не нужно их обновлять в Entity, поскольку они не изменились в контракте, нам нужно игнорировать их в определении сопоставления:

ForMember(dest => dest.RefundType, opt => opt.Ignore())

Итак, мой код получился таким:

Mapper.CreateMap<MyDataContract, MyEntity>
ForMember(dest => dest.NavigationProperty1, opt => opt.Ignore())
ForMember(dest => dest.NavigationProperty2, opt => opt.Ignore())
.IgnoreAllNonExisting();
person Nayyar Abbas    schedule 16.07.2020

Эта проблема возникает из-за того, что мы пытаемся удалить родительскую таблицу, по-прежнему присутствуют данные дочерней таблицы. Решаем проблему каскадным удалением.

Метод Create в модели в классе dbcontext.

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

После этого в нашем вызове API

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

Параметр Каскадное удаление позволяет удалить родительскую и связанную дочернюю таблицу с помощью этого простого кода. Сделайте попытку таким простым способом.

Удалить диапазон, который использовался для удаления списка записей в базе данных Спасибо

person Sowmiya V    schedule 30.01.2017

Я также решил свою проблему с помощью ответа Моша, и я подумал Ответ PeterB был не совсем точным, поскольку он использовал перечисление в качестве внешнего ключа. Помните, что вам нужно будет добавить новую миграцию после добавления этого кода.

Я также могу порекомендовать этот пост в блоге для других решений:

http://www.kianryan.co.uk/2013/03/orphaned-child/

Код:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}
person Ogglas    schedule 07.07.2017

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

Все мои постоянные объекты реализуют этот интерфейс

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

Таким образом, я реализовал эти две функции в своем репозитории.

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

Чтобы использовать его, я делаю следующее:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

Надеюсь это поможет


ДОПОЛНИТЕЛЬНО: вы также можете создать отдельный класс DbContextExtentions (или свой собственный контекстный интерфейс):

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

и используйте его как:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);
person Bluemoon74    schedule 28.01.2019
comment
Вы также можете создать класс расширения для своего контекста с помощью следующих функций: - person Bluemoon74; 28.01.2019

У меня была такая же проблема, когда я собирался удалить свою запись, чем возникла какая-то проблема, поскольку решение этой проблемы заключается в том, что когда вы собираетесь удалить свою запись, вам не хватает чего-то перед удалением заголовка / основной записи, вы должны написать код для удалите его деталь перед заголовком / мастером. Надеюсь, проблема будет решена.

person Ghazi Hur    schedule 23.04.2019

Я столкнулся с этой проблемой за несколько часов и попробовал все, но в моем случае решение отличалось от указанного выше.

Если вы используете уже извлеченный объект из базы данных и попытаетесь изменить его дочерние элементы, возникнет ошибка, но если вы получите свежую копию объекта из базы данных, проблем возникнуть не должно. Не используйте это:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

Использовать этот:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
person Tanyo Ivanov    schedule 05.01.2015