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

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

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

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

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

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

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

Строки кода можно перевести на:

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

21 ответ

Решение

Вы должны удалить старые дочерние элементы 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 и затем код выглядит немного иначе.) Я также убрал все абстракции репозитория, чтобы сделать его простым.

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

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

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

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

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

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

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

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

Решения:

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

context.Children.RemoveRange(parent.Children);

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

parent.Children.Clear();

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

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

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

Если вы используете 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();

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

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

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

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

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

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

Вы должны правильно определить родительско-дочерние отношения, используя " идентифицирующие отношения".

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

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

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

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

ОБНОВИТЬ

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

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

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

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

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

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

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

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

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 использует, чтобы решить, "имеют ли дети смысл" без родителя (в этом случае Clear() не удалит их и не выдаст исключение, если вы не установите ParentId на что-то другое / специальное), или - как в исходном вопросе - мы ожидаем, что элементы будут удалены после удаления из родительского.

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

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

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

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

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

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

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

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

И модификация, которую 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 правильно пометит сущность как удаленную.

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

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

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

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

Что сработало для меня, так это сначала получить дочерние элементы, используя 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

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

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) это нормально.

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

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

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

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

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

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

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

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

Если вы используете 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();

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

В модели 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 Call

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

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

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

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

Я также решил мою проблему с ответом Моша, и я подумал, что ответ 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; }
}

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

      entity.GetDbContextFromEntity().Entry(entity).Reference(i => i.ParentEntity).Query().Where(p => p.ID == 1).Load();

Просто совет, убедившись, что целевая сущность не ссылается ни на одного родителя.

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

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

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

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

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

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
Другие вопросы по тегам