Почему 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 просто знал, что они тот же самый объект, основанный на одном ID.

Что касается того, почему EF делает это, одно из объяснений может заключаться в том, что он пытается избежать выполнения операций multipe 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 ответа

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

    CreatedById = user1.Id, 
    ModifiedById = user1.Id 
    

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

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

  2. Если вы получаете 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
    };
    

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

  3. Объяснение в том, что EF изменения tracker является картой идентичности . Т. е. запись в базе данных сопоставляется с одним и только одним объектом 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)
    

    … надо. Но достаточно прочная.