Добавление отдельных объектов в отношение 1-много в EF 4.1 Code First - PullRequest
4 голосов
/ 14 ноября 2011

Я пытаюсь использовать код EF 4.1 First, чтобы смоделировать простые отношения пользователя, имеющего одну роль. Когда я пытаюсь сохранить существующего пользователя с новой ролью в другом контексте (использующем другой контекст для имитации обхода клиент-сервер), я получаю следующее исключение:

System.Data.Entity.Infrastructure.DbUpdateException: произошла ошибка при сохранении сущностей, которые не предоставляют свойства внешнего ключа для своих отношений. Свойство EntityEntries вернет значение NULL, поскольку один объект не может быть определен как источник исключения. Обработка исключений при сохранении может быть упрощена путем предоставления свойств внешнего ключа в типах объектов. Смотрите InnerException для подробностей. ---> System.Data.UpdateException: отношение из AssociationSet 'User_CurrentRole' находится в состоянии 'Добавлено'. Учитывая ограничения множественности, соответствующий «User_CurrentRole_Source» также должен находиться в состоянии «Добавлено».

Я ожидаю, что новая роль будет создана и связана с существующим пользователем.
Что я делаю не так, возможно ли в первую очередь добиться этого в коде EF 4.1? Похоже, что сообщение об ошибке указывает на то, что ему нужно, чтобы и пользователь, и роль были в добавленном состоянии, но я изменяю существующего пользователя, так как это может быть?

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

Вот сущности:

public class User
{
    public int UserId { get; set; }
    public string Name { get; set; }
    public Role CurrentRole { get; set; }
}

public class Role
{
    public int RoleId { get; set; }
    public string Description { get; set; }
}

А вот отображение, которое я использую:

public class UserRolesContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Role> Roles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>().HasKey(u => u.UserId);
        modelBuilder.Entity<Role>().HasKey(r => r.RoleId);
        modelBuilder.Entity<User>().HasRequired(u => u.CurrentRole);
    }
}

Я предварительно заполнил базу данных этим:

public class UserInitializer : DropCreateDatabaseAlways<UserRolesContext>
{
        protected override void Seed(UserRolesContext context)
        {
            context.Users.Add(new User() {Name = "Bob", 
                                          CurrentRole = new Role() {Description = "Builder"}});
            context.SaveChanges();
        }
}

И, наконец, вот неудачный тест:

    [TestMethod]
    public void CanModifyDetachedUserWithRoleAndReattach()
    {
        Database.SetInitializer<UserRolesContext>(new UserInitializer());
        var context = new UserRolesContext();

        // get the existing user
        var user = context.Users.AsNoTracking().Include(c => c.CurrentRole).First(u => u.UserId == 1);

        //modify user, and attach to a new role
        user.Name = "MODIFIED_USERNAME";
        user.CurrentRole = new Role() {Description = "NEW_ROLE"};

        var newContext = new UserRolesContext();
        newContext.Users.Attach(user);
        // attachment doesn't mark it as modified, so mark it as modified manually
        newContext.Entry(user).State = EntityState.Modified;
        newContext.Entry(user.CurrentRole).State = EntityState.Added;

        newContext.SaveChanges();

        var verificationContext = new UserRolesContext();
        var afterSaveUser = verificationContext.Users.Include(c => c.CurrentRole).First(u => u.UserId == 1);
        Assert.AreEqual("MODIFIED_USERNAME", afterSaveUser.Name, "User should have been modified");
        Assert.IsTrue(afterSaveUser.CurrentRole != null, "User used to have a role, and should have retained it");
        Assert.AreEqual("NEW_ROLE", afterSaveUser.CurrentRole.Description, "User's role's description should  have changed.");
    }
}
}

Конечно, это сценарий, который я рассмотрел, я думаю, что я чего-то упускаю в том, как я определил отображение модели?

1 Ответ

8 голосов
/ 14 ноября 2011

Вы нарушили модель состояния EF.Вы сопоставили свою сущность с обязательным CurrentRole, поэтому EF знает, что у вас не может быть существующего User без Role.Вы также использовали независимых ассоциаций (на вашей организации не выставлялась собственность FK).Это означает, что отношение между ролью и пользователем является еще одной отслеживаемой записью, которая имеет свое состояние.Когда вы назначаете роль существующему пользователю, запись отношения имеет состояние Added, но это невозможно для существующего User (потому что для него уже должна быть назначена роль), если вы не пометите старое отношение как Deleted (или есливы работаете с новым пользователем).Решить это в отдельном сценарии очень сложно , и это приводит к коду, в котором вы должны передавать информацию о старой роли во время туда-обратно и вручную играть с менеджером состояний или с самим графом сущностей.Примерно так:

Role newRole = user.CurrentRole; // Store the new role to temp variable
user.CurrentRole = new Role { Id = oldRoleId }; // Simulate old role from passed Id

newContext.Users.Attach(user);
newCotnext.Entry(user).State = EntityState.Modified;
newContext.Roles.Add(newRole);

user.CurrentRole = newRole; // Reestablish the role so that context correctly set the state of the relation with the old role

newContext.SaveChanges();

Самое простое решение - загрузить старое состояние из базы данных и объединить изменения из нового состояния в загруженное (подключенное).Этого также можно избежать, раскрыв свойства FK.

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

...