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

我试图使用EF 4.1 Code First来建模具有单个角色的用户的简单关系.当我尝试将具有新角色的现有用户保存到不同的上下文(使用不同的上下文来模拟客户端 – 服务器往返)时,我得到以下异常:

System.Data.Entity.Infrastructure.dbupdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. —> System.Data.UpdateException: A relationship from the ‘User_CurrentRole’ AssociationSet is in the ‘Added’ state. Given multiplicity constraints,a corresponding ‘User_CurrentRole_Source’ must also in the ‘Added’ state.

我期望创建一个新角色并与现有用户相关联.
我做错了什么,这有可能首先在EF 4.1代码中实现吗?错误消息似乎表明它需要用户和角色都处于添加状态,但我正在修改一个exising用户,那怎么可能呢?

注意事项:我想避免修改实体的结构(例如,通过引入实体上可见的外键属性),并且在数据库中我希望用户一个指向Role的外键(不是其他方式).我也不准备转移到自我跟踪实体(除非没有别的办法).

以下是实体:

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

当然这是一个被覆盖的场景,我猜这是我在定义模型映射的方式中缺少的东西?

解决方法

你已经破坏了EF状态模型.您使用强制CurrentRole映射了您的实体,因此EF知道您不能拥有没有角色的现有用户.您还使用了 independent associations(在您的实体上没有暴露的FK属性).这意味着角色和用户间的关系是另一个具有其状态的被跟踪条目.将角色分配给现有用户时,关系条目的状态设置为已添加,但现有用户不可能(因为它必须已分配角色),除非您将旧关系标记为已删除(或除非您正在使用新的关系)用户).在分离的场景中解决这个问题是 very hard,它导致代码必须在往返过程中传递有关旧角色的信息,并手动使用状态管理器或实体图本身.就像是:

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属性也可以避免这种情况.

顺便说一句.你的模型不是一对一的,而是一对一的角色可以分配给多个用户 – 如果是一对一的话,它会更复杂,因为你必须在创建一个新角色之前删除旧角色.

相关文章

目录简介使用JS互操作使用ClipLazor库创建项目使用方法简单测...
目录简介快速入门安装 NuGet 包实体类User数据库类DbFactory...
本文实现一个简单的配置类,原理比较简单,适用于一些小型项...
C#中Description特性主要用于枚举和属性,方法比较简单,记录...
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机...
目录简介获取 HTML 文档解析 HTML 文档测试补充:使用 CSS 选...