Подтвердить что ты не робот

Entity Framework, создающая новый объект с отношением к существующей сущности, приводит к попытке создать новую копию существующего объекта

Я пытаюсь создать новый пользовательский объект с определенной ролью. "Роль" - это существующий объект в EF. У меня есть googled, и stackoverflowed до тех пор, пока я не синюсь в лицо, и я пробовал все вещи, которые, кажется, работают для всех остальных. Но когда я пытаюсь сохранить свой новый пользовательский объект, он сначала пытается создать новую "роль", а не просто создавать новый пользовательский объект со ссылкой на существующую роль.

Что я делаю неправильно?

Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;

if (myUser.ID == 0)
{
    myObjectContext.Users.AddObject(myUser);
}
else
{
    if (myUser.EntityState == System.Data.EntityState.Detached)
    {
        myObjectContext.Users.Attach(myUser);
    }
    myObjectContext.ObjectStateManager.ChangeObjectState(myUser, System.Data.EntityState.Modified);
}
myObjectContext.SaveChanges(SaveOptions.None);

ИЗМЕНИТЬ - ПОСЛЕ БОЛЬШЕ ТЕСТИРОВАНИЯ...

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

В принципе, есть два набора данных, которые я прикрепляю к моему новому объекту User. Первая - это "Роль", которая является FK для таблицы ролей, которая содержит Роль. Это отображается как свойство навигации для пользователя, например "User.Role".

Второй набор данных представляет собой набор объектов, называемых "FIPS", которые являются отношениями "многие ко многим" между Пользователем и другой таблицей, называемой FIPS. Между ними существует таблица отношений, которая просто содержит два столбца, каждый из которых является внешним ключом для пользователя и FIPS, соответственно. FIPS для пользователя также является навигационным свойством, на которое ссылаются как "User.FIPS".

Вот весь код, показывающий назначение объекта FIPS и роли для пользователя перед сохранением контекста.

List<string> fipsList = new List<string>();
foreach (FIPS fips in myUser.FIPS)
{
    fipsList.Add(fips.FIPS_Code);
}
myUser.FIPS.Clear();
foreach (string fipsCode in fipsList)
{
    FIPS myFIPS = new FIPS { FIPS_Code = fipsCode };
    myObjectContext.FIPSCodes.Attach(myFIPS);
    myUser.FIPS.Add(myFIPS);
}


Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;


if (myUser.ID == 0)
{
   myObjectContext.Users.AddObject(myUser);
}
else
{
   if (myUser.EntityState == System.Data.EntityState.Detached)
   {
       myObjectContext.Users.Attach(myUser);
   }
   myObjectContext.ObjectStateManager.ChangeObjectState(myUser, System.Data.EntityState.Modified);
}

myObjectContext.SaveChanges(SaveOptions.None);

Я установил свои часы, чтобы проверить статус "myObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added)", чтобы увидеть, когда к этому добавились вещи.

Как только первый Связанный объект добавляется к объекту User, второй Связанный объект, который еще не был присоединен к контексту, добавляется в контекст с EntityState из "Добавлен".

.... Посмотрим, есть ли способ избежать присоединения связанных объектов к объекту User до тех пор, пока все они не будут привязаны к контексту.

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

  • Привязать все связанные сущности к контексту.
  • Удалить существующие отношения для объекта пользователя для связанного объекта типы.
  • Назначить связанные объекты объекту пользователя.
  • Сохранить пользовательский объект.

И.. теперь.. он работает.. omg это работает...! =)

4b9b3361

Ответ 1

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

В следующем коде я сначала проверяю ChangeTracker и использую существующую запись, если роль отслеживается.

// add roles that are in dto.Roles, but not in resource.Roles
// use the change tracker entry, or add a stub role
var rolesToAdd = fromDto.Roles.Where(r => !toResource.Roles.Any(role => role.Id == r)).ToList();
var roleEntries = dbContext.ChangeTracker.Entries<Role>();

foreach (var id in rolesToAdd)
{
    var role = roleEntries.Where(e => e.Entity.Id == id).Select(e => e.Entity).FirstOrDefault();

    if (role == null)
    {
        role = new Role { Id = id };
        dbContext.Set<Role>().Attach(role);
    }

    toResource.Roles.Add(role);
}

Ответ 2

Почему вы создаете новый экземпляр вашего объекта Role, если он уже существует в базе данных?

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

Role myRole = new Role { ID = myUser.Role.ID };
myObjectContext.Roles.Attach(myRole);
myUser.Role = myRole;

Сначала вы создаете новую роль, у которой есть идентификатор, который поступает из существующего экземпляра Role (myUser.Role), затем вы присоединяете свой новый экземпляр, а затем, наконец, снова затрагиваете свой экземпляр для пользователя, из которого он пришел. Там определенно что-то не так. Если ваша роль уже существует (и, похоже, это так, как вы написали myUser.Role.ID в первой строке, поэтому я предполагаю), почему вы создаете новый экземпляр.

Отбросьте эти 3 строки. Получите свою роль из базы данных. Затем примените Role, который поступает из базы данных в свойство myUser.Role.

Ответ 3

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

int id = myUser.Role.ID; // Role should be NULL, if the user is actually new...
                         // could it be that you wanted to write myUser.RoleID?
Role myRole = myObjectContext.Roles.FirstOrDefault(x => x.ID == id);
myUser.Role = myRole;