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

192

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

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

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

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

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

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

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

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
яффа
источник
Я нашел свой ответ на покупку с использованием решения № 2 в статье ниже, в основном я создал добавленный первичный ключ к дочерней таблице для ссылки на родительскую таблицу (поэтому он имеет 2 первичных ключа (внешний ключ для родительской таблицы и идентификатор). для дочернего стола). c-sharpcorner.com/UploadFile/ff2f08/…
переговоров
@jaffa, я нашел свой ответ здесь stackoverflow.com/questions/22858491/…
Антонио

Ответы:

159

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

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

Slauma
источник
Так что, если некоторые только изменились? Означает ли это, что я все еще должен удалить их и добавить их снова?
Яффо
@Jon: нет, вы также можете обновить существующие элементы, конечно. Я добавил пример того, как я мог бы обновить дочернюю коллекцию, см. Раздел «Редактирование» выше.
Слаума
@Slauma: Lol, если бы я знал, что ты собираешься изменить свой ответ, я бы не написал свой ответ ...
Ладислав Мрнка,
@Ladislav: Нет, нет, я рад, что вы написали свой собственный ответ. Теперь, по крайней мере, я знаю, что это не полная чушь и не слишком сложная вещь, которую я сделал выше.
Слаума
1
Я бы добавил условие при извлечении originalChildItem в foreach: ... Где (c => c.ID == childItem.ID && c.ID! = 0) в противном случае он вернет вновь добавленных потомков, если childItem.ID == 0.
perfect_element
116

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

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

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

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

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

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

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

Решения:

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

context.Children.RemoveRange(parent.Children);

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

parent.Children.Clear();
Мош
источник
9
Я нашел это объяснение наиболее полезным.
Booji Boy
7
Хорошее объяснение композиции против агрегации и как структура сущности связана с ней.
Chrysalis
# 1 был наименьшим количеством кода, необходимого для решения проблемы. Спасибо!
ryanulit
73

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

  • Вы загружаете 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();
Ладислав Мрнка
источник
1
Но есть ваше интересное замечание об использовании .Clone(). Имеете ли вы в виду, что a ChildItemимеет другие дочерние свойства навигации? Но в таком случае, не хотим ли мы, чтобы весь подграф был привязан к контексту, поскольку мы ожидаем, что все дочерние элементы являются новыми объектами, если сам дочерний элемент является новым? (Ну, это может отличаться от модели к модели, но давайте предположим, что дочерние элементы «зависят» от ребенка, как дочерние элементы зависят от родителя.)
Слаума
Вероятно, потребуется «умный» клон.
Ладислав Мрнка
1
Что делать, если вы не хотите иметь коллекцию ребенка в вашем контексте? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Кирстен Жадность
1
parent.ChildItems.Remove (ребенок); context.Childs.Remove (ребенок); Это двойное удаление исправлено может выпустить, СПАСИБО. Зачем нам оба удаления? Почему удаление только из parent.ChildItems не достаточно, поскольку childs живет только как childs?
Фернандо Торрес
40

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

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

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

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

Грег Литтл
источник
Я считаю, что это правильный ответ.
Десмати
логичное и прямолинейное решение.
sairfan
19

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

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

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

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

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

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

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

ОБНОВИТЬ

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

Андре Луус
источник
Настройка как идентификация отношений здесь не поможет, потому что сценарий в вопросе имеет дело с отсоединенными объектами ( «мой новый список, который происходит из представления MVC» ). Вам все еще нужно загрузить исходные дочерние элементы из БД, найти удаленные элементы в этой коллекции на основе отдельной коллекции, а затем удалить из БД. Разница лишь в том, что с идентифицирующими отношениями вы можете звонить parent.ChildItems.Removeвместо _dbContext.ChildItems.Remove. По-прежнему (EF <= 6) нет встроенной поддержки EF, чтобы избежать длинного кода, подобного тому, который содержится в других ответах.
Слаума
Я понимаю вашу точку зрения. Тем не менее, я считаю, что с пользовательским механизмом связывания модели, который загружает сущность из контекста или возвращает новый экземпляр, вышеприведенный подход будет работать. Я обновлю свой ответ, чтобы предложить это решение.
Андре Луус
Да, вы могли бы использовать связыватель модели, но теперь вы должны были сделать то же самое из других ответов в связывателе модели. Он просто перемещает проблему из уровня репо / сервиса в связующее звено модели. По крайней мере, я не вижу реального упрощения.
Слаума
Упрощение - автоматическое удаление осиротевших объектов. Все, что вам нужно в связующем модели, это общий эквивалентreturn context.Items.Find(id) ?? new Item()
Андре Луус
Хорошие отзывы для команды EF, но, к сожалению, предложенное вами решение ничего не решает на земле EF.
Крис Москини
9

Если вы используете 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
jsgoupil
источник
Я столкнулся с аналогичной проблемой с AutoMapper, но это не работает для меня :( См stackoverflow.com/q/41430679/613605
J86
4

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

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

Мартин
источник
4

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

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

Маурисио Рамальо
источник
2

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

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

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

Вагнер Бертолини Джуниор
источник
Происходит ли повторная вставка с новыми идентификаторами или она сохраняет идентификаторы ребенка, которые были у них в первую очередь?
Пепито Фернандес
2

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

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

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
Дино Бансиган
источник
2

Вы должны вручную очистить коллекцию 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);
    }
}
Суббота
источник
Это хорошо сработало для меня. Мне просто нужно было добавить context.DetectChanges();.
Энди Эдинборо
1

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

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

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

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

Стив
источник
1

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

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

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; }
}
Петерб
источник
1

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

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 будет достаточно).

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

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

Ekus
источник
0

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

В модели 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();

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

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

Совмия V
источник
0

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

Используя решение 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);
Bluemoon74
источник
Вы также можете создать класс расширения для вашего контекста с помощью этих функций:
Bluemoon74
0

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

Гази Хур
источник
0

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

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

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

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();
Найяр Аббас
источник
-1

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

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

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

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

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
Танё Иванов
источник