简体   繁体   English

在EF 4.1 Code First中将分离的实体添加到1-many关系中

[英]Adding detached entities to a 1-many relationship in EF 4.1 Code First

I am attempting to use EF 4.1 Code First to model a simple relationship of a User having a single Role. 我试图使用EF 4.1 Code First来建模具有单个角色的用户的简单关系。 When I attempt to save an existing User with a new Role to a different context (using a different context to simulate a client-server round-trip), I get the following exception: 当我尝试将具有角色的现有用户保存到不同的上下文(使用不同的上下文来模拟客户端 - 服务器往返)时,我得到以下异常:

System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. System.Data.Entity.Infrastructure.DbUpdateException:保存不公开其关系的外键属性的实体时发生错误。 The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. EntityEntries属性将返回null,因为无法将单个实体标识为异常源。 Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. 通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常。 See the InnerException for details. 有关详细信息,请参阅InnerException。 ---> System.Data.UpdateException: A relationship from the 'User_CurrentRole' AssociationSet is in the 'Added' state. ---> System.Data.UpdateException:来自'User_CurrentRole'AssocSet的关系处于'已添加'状态。 Given multiplicity constraints, a corresponding 'User_CurrentRole_Source' must also in the 'Added' state. 给定多重约束,相应的'User_CurrentRole_Source'也必须处于'已添加'状态。

What I expect is that a new Role is created and associated with the exising User. 我期望创建一个新角色并与现有用户相关联。
What am I doing wrong, is this possible to achieve in EF 4.1 code first? 我做错了什么,这有可能首先在EF 4.1代码中实现吗? The error message seems to suggest that it needs both the User and the Role to be in the added state, but I'm modifying an exising User, so how can that be? 错误消息似乎表明它需要用户和角色都处于添加状态,但我正在修改一个exising用户,那怎么可能呢?

Things to note: I'd like to avoid modifying the structure of the entities (eg by introducing foreign key properties visible on the entities), and in the database I'd like the User to have a foreign key pointing to Role (not the other way around). 注意事项:我想避免修改实体的结构(例如,通过引入实体上可见的外键属性),并且在数据库中我希望用户有一个指向Role的外键(不是其他方式)。 I'm also not prepared to move to Self Tracking Entities (unless there's no other way). 我也不准备转移到自我跟踪实体(除非没有别的办法)。

Here are the entities: 以下是实体:

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; }
}

And here's the mapping I'm using: 这是我正在使用的映射:

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);
    }
}

I pre-populate the database with this: 我用这个预先填充数据库:

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();
        }
}

And finally, here's the failing test: 最后,这是失败的测试:

    [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.");
    }
}
}

Surely this is a scenario that's covered, I would guess it's something I'm missing in the way I've defined the model mapping? 当然这是一个被覆盖的场景,我猜这是我在定义模型映射的方式中缺少的东西?

You have broken EF state model. 你已经破坏了EF状态模型。 You mapped your entity with mandatory CurrentRole so EF knows that you cannot have existing User without the Role . 您使用强制CurrentRole映射了您的实体,因此EF知道您不能拥有没有Role现有User You have also used independent associations (no FK property exposed on your entity). 您还使用了独立关联 (在您的实体上没有公开FK属性)。 It means that relation between role and user is another tracked entry which has its state. 这意味着角色和用户之间的关系是另一个具有其状态的被跟踪条目。 When you assign the role to existing user the relation entry has state set to Added but it is not possible for existing User (because it must have already role assigned) unless you mark the old relation as Deleted (or unless you are working with a new user). 将角色分配给现有用户时,关系条目的状态设置为已Added但现有User不可能(因为它必须已分配角色),除非您将旧关系标记为Deleted (或除非您正在使用新的用户)。 Solving this in detached scenario is very hard and it leads to the code where you must pass information about old role during the roundtrip and manually play with state manager or with entity graph itself. 在分离的场景中解决这个问题非常困难 ,它会导致代码中必须在往返过程中传递有关旧角色的信息,并手动使用状态管理器或实体图本身。 Something like: 就像是:

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();

The simplest solution is load the old state from the database and merge changes from the new state to the loaded (attached) one. 最简单的解决方案是从数据库加载旧状态,并将更改从新状态合并到加载(附加)状态。 This can be also avoided by exposing FK properties. 通过暴露FK属性也可以避免这种情况。

Btw. 顺便说一句。 your model is not one to one but one to many where the role can be assigned to multiple users - in case of one-to-one it would be even more complicated because you will have to delete the old role prior to creating a new one. 你的模型不是一对一的,而是一对一的角色可以分配给多个用户 - 如果是一对一的话,它会更复杂,因为你必须在创建一个新角色之前删除旧角色。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM