Почему EF не может обрабатывать два свойства с одним и тем же внешним ключом, но с отдельными ссылками / экземплярами?

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

var user1 = new AppUser { Id = 1 };
var user2 = new AppUser { Id = 1 };

var address = new Address
{
    CreatedBy = user1, //different reference
    ModifiedBy = user2 //different reference
};

Когда я пытаюсь вставить эту запись, EF выдает это исключение:

Saving or accepting changes failed because more than one entity of type
'AppUser' have the same primary key value. [blah blah blah]

Я обнаружил, что это решает проблему:

var user1 = new AppUser { Id = 1 };
var user2 = user1; //same reference

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

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

Обновить

Так что, как я и подозревал, в моем последнем абзаце выше. В отсутствие средства, чтобы сказать EF не делать CRUD ни в одном из случаев, я пока просто сделаю это:

if (address.ModifiedBy.Id == address.CreatedBy.Id)
{
    address.ModifiedBy = address.CreatedBy;
}

Работает достаточно хорошо, пока я не пытаюсь использовать CRUD.

Update2

Ранее я прибегал к этому, чтобы не допустить проверки EF обязательных свойств NULL, если все, что мне нужно, это идентификатор дочерней сущности. Тем не менее, это не удерживает EF от волнения по отдельным экземплярам с одинаковым идентификатором. Если это не будет делать CRUD ни на одном AppUser объект, почему это важно, если экземпляры разные?

foreach (var o in new object[] { address.ModifiedBy, address.CreatedBy })
{
    db.Entry(o).State = EntityState.Unchanged;
}

3 ответа

Если вы получаете AppUser Исходя из контекста, вам не нужно ничего делать, потому что Entity Framework будет отслеживать сущности:

var user1 = context.AppUsers.Find(1);
var user2 = context.AppUsers.Find(1);

var address = new Address
{
    CreatedBy = user1, //different reference
    ModifiedBy = user2 //different reference
};

Теперь они оба будут указывать на одни и те же объекты и не будут вызывать конфликт.

Вы можете добавить два дополнительных свойства, чтобы иметь идентификатор для основных объектов, который является AppUserтогда вы можете использовать только один AppUser Объект и ссылаться на него как для созданных и измененных свойств.

CreatedById = user1.Id, 
ModifiedById = user1.Id 

В противном случае ваш код закончится сохранением двух экземпляров AppUser с тем же первичным ключом.

Другой подход состоит в том, чтобы установить оба свойства внешнего ключа только на один AppUserобъект

Объяснение состоит в том, что трекер изменений EF является картой идентичности. Т.е. запись в базе данных сопоставляется одному и только одному объекту CLR.

Это можно легко продемонстрировать, если попытаться присоединить два объекта с одним и тем же ключом:

context.AppUsers.Attach(new AppUser { Id = 1 });
context.AppUsers.Attach(new AppUser { Id = 1 });

Вторая строка выдаст исключение:

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

Это также происходит, если вы назначаете

CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference

Где-то в процессе, user1 а также user2 должны быть привязаны к контексту, что приводит к исключению, которое вы получаете.

Видимо, у вас есть функция, которая получает два Id значения, которые могут быть разными или идентичными. Правда, было бы очень удобно, если бы вы могли просто создать два AppUser примеры из этих Ids, не нужно беспокоиться об идентичных ключах. К сожалению, ваше решение...

if (address.ModifiedBy.Id == address.CreatedBy.Id)

... является необходимым. Впрочем, достаточно солидно.

Другие вопросы по тегам