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

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

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

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

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

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

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

Линии кода можно перегонять до:

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

Ответ 1

Вы должны вручную удалить старые дочерние элементы 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 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 a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

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

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

Ответ 2

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

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

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

Способ, которым Entity Framework отличает агрегирование и составные отношения, таков:

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

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

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

Решения:

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

context.Children.RemoveRange(parent.Children);

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

parent.Children.Clear();

Ответ 3

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

  • Вы загружаете Parent из базы данных и получаете прикрепленный объект
  • Вы заменяете свою дочернюю коллекцию новой коллекцией отдельных детей
  • Вы сохраняете изменения, но во время этой операции все дети считаются добавленными. becasue 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();

Ответ 4

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

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

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

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

Ответ 5

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

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

Вы должны правильно определить отношения между родителями и дочерними элементами, используя ссылку

Кроме вышеперечисленного, вам может понадобиться (из опыта NHibernate)

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

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

UPDATE

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

Ответ 6

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

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

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

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

Задайте свой автомастер для игнорирования свойства ClassB.

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId

Ответ 7

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

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

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

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

где X следует заменить удаленным Entity.

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

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

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

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

Ответ 8

У меня была такая же ошибка. У меня есть две таблицы с родительскими отношениями с дочерними элементами, но я сконфигурировал "on delete cascade" в столбце внешнего ключа в определении таблицы дочерней таблицы. Поэтому, когда я вручную удаляю родительскую строку (через 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(); 
   }    

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

Ответ 9

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

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

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

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

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

Ответ 10

Вы должны вручную очистить коллекцию 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);
    }
}

Ответ 11

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

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

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

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

Ответ 12

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

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) все в порядке.

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

Ответ 13

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

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

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

Ответ 14

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

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

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

Используйте это:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }

Ответ 15

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

В модели Создайте метод в классе 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();

Каскадное удаление. Удалите родительскую, а также родительскую дочернюю таблицу с помощью этого простого кода. Попробуйте это простым способом.

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

Ответ 16

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

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

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

Ответ 17

Используя решение 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 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 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 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 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);