Причина ошибки в приведенном коде заключается в следующем.
Когда вы получаете созданную сущность A
из базы данных, ее свойство S
инициализируется коллекцией, которая содержит две новые записи B
. Id
каждого из этих новых B
объектов равен 0
.
// This line of code reads entity from the database
// and creates new instance of object A from it.
var a = db.Set<A>().Single();
// When new entity A is created its field S initialized
// by a collection that contains two new instances of entity B.
// Property Id of each of these two B entities is equal to 0.
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
После выполнения строки кода var a = db.Set<A>().Single()
коллекция S
сущностей A
не содержит B
сущностей из базы данных, поскольку DbContext Db
не использует отложенную загрузку и нет явной загрузки коллекции S
. Сущность A
содержит только новые B
сущности, которые были созданы во время инициализации коллекции S
.
Когда вы вызываете IsModifed = true
коллекцию S
сущностей, структура пытается добавить эти два новых объекта B
в отслеживание изменений. Но это терпит неудачу, потому что оба новых B
объекта имеют то же самое Id = 0
:
// This line tries to add to change tracking two new B entities with the same Id = 0.
// As a result it fails.
db.Entry(a).Collection(x => x.S).IsModified = true;
Из трассировки стека видно, что структура сущностей пытается добавить B
сущности в IdentityMap
:
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(Object relatedEntity, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.set_IsModified(Boolean value)
И сообщение об ошибке также говорит, что он не может отслеживать B
сущность, Id = 0
потому что другая B
сущность с таким же Id
уже отслежена.
Как решить эту проблему.
Чтобы решить эту проблему, вы должны удалить код, который создает B
сущности при инициализации S
коллекции:
public ICollection<B> S { get; set; } = new List<B>();
Вместо этого вы должны заполнить S
коллекцию на месте, где A
создается. Например:
db.Add(new A {S = {new B(), new B()}});
Если вы не используете отложенную загрузку, вам следует явно загрузить S
коллекцию, чтобы добавить ее элементы в отслеживание изменений:
// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
Почему он не добавляет, а не прикрепляет экземпляры B?
Короче говоря , они прикреплены вместо добавления, потому что они имеют Detached
состояние.
После выполнения строки кода
var a = db.Set<A>().Single();
созданные экземпляры объекта B
имеют состояние Detached
. Это можно проверить с помощью следующего кода:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Затем, когда вы установите
db.Entry(a).Collection(x => x.S).IsModified = true;
EF пытается добавить B
объекты для отслеживания изменений. Из исходного кода EFCore вы можете видеть, что это приводит нас к методу InternalEntityEntry.SetPropertyModified со следующими значениями аргумента:
property
- одна из наших B
организаций,
changeState = true
,
isModified = true
,
isConceptualNull = false
,
acceptChanges = true
,
Этот метод с такими аргументами изменяет состояние Detached
B
объектов Modified
, а затем пытается начать их отслеживание (см. Строки 490 - 506). Поскольку B
сущности теперь имеют состояние, Modified
это приводит к их присоединению (не добавлению).