Ily*_*lev 4 c# entity-framework entity-framework-4 ef-code-first
我试图使用EF 4.1 Code First来建模具有单个角色的用户的简单关系.当我尝试将具有新角色的现有用户保存到不同的上下文(使用不同的上下文来模拟客户端 - 服务器往返)时,我得到以下异常:
System.Data.Entity.Infrastructure.DbUpdateException:保存不公开其关系的外键属性的实体时发生错误.EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源.通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常.有关详细信息,请参阅InnerException.---> System.Data.UpdateException:来自'User_CurrentRole'AssocSet的关系处于'已添加'状态.给定多重约束,相应的'User_CurrentRole_Source'也必须处于'已添加'状态.
我期望创建一个新角色并与现有用户相关联.
我做错了什么,这有可能首先在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; }
}
Run Code Online (Sandbox Code Playgroud)
这是我正在使用的映射:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
我用这个预先填充数据库:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这是失败的测试:
[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.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
当然这是一个被覆盖的场景,我猜这是我在定义模型映射的方式中缺少的东西?
你已经破坏了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();
Run Code Online (Sandbox Code Playgroud)
最简单的解决方案是从数据库加载旧状态,并将更改从新状态合并到加载(附加)状态.通过暴露FK属性也可以避免这种情况.
顺便说一句.你的模型不是一对一的,而是一对一的角色可以分配给多个用户 - 如果是一对一的话,它会更复杂,因为你必须在创建一个新角色之前删除旧角色.
| 归档时间: |
|
| 查看次数: |
6860 次 |
| 最近记录: |