我有一个带有映射表的代码优先模型,以便我可以将MenuItem映射到IdentityRole,从而根据登录用户角色分配生成菜单。
public class MenuItem
{
public int Id { get; set; }
public string Text { get; set; }
}
public class MenuRoleMap
{
[Key]
public int Id { get; set; }
public virtual MenuItem MenuItem { get; set; }
public virtual IdentityRole Role { get; set; }
}
IdentityRole和Identity的其余部分通过IdentityDbContext自动连接,我通过我的ApplicationDbContext继承了这个,然后上下文应该是一致的。
public class ApplicationDbContext : IdentityDbContext<User>
所有表看起来都正确,它们有预期的列和外键,这里是MenuRoleMap表
我有一个有效的现有MenuItem和IdentityRole实例,我用它来尝试向该表添加一个新的实体项
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges(); /// <<<=== HERE ERROR BECAUSE THE role IS ALREADY IN DB
会抛出此错误
A first chance exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll
Role: Role SystemsAdministrator already exists.
当然,它确实存在,我知道,它已经存在于数据库中。当然,EF不应该尝试为外键实体添加新的实体项(如果它已经存在)?
它不是针对MenuItem,只针对IdentityRole。
我认为问题是代理创建,因为IdentityRole是一个代理对象,所以我把它关掉了
this.Configuration.ProxyCreationEnabled = false;
但我仍然得到同样的错误。
我的问题是,如何添加外键为IdentityRole的实体?
谢谢堆叠器。
回答我自己的问题
在下面的那些人的帮助下,我在调查各种解决方案时发现,问题本身不是一个上下文,而是一个对象的有效性。对象看起来正确,我没有意识到的是它不是来自上下文的对象,它是一个传真。通过尝试将此传真添加到模型中,上下文非常正确地说它已经存在,您无法再次添加它。通过尝试覆盖项目的状态,我创建了一种不同的错误。
解决方案只是从上下文重新加载对象,然后将其添加到父项目中,如此
foreach (IdentityRole role in selectedRoles)
{
// Here I'm getting the role from the context using the ID I have from the facsimile
IdentityRole roleToUse = db.Roles.Where(x => x.Id == role.Id).FirstOrDefault();
// carry on as normal
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = roleToUse; // note I'm using the retrieved 'roleToUse'
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges();
嘿presto一切正常。
答案 0 :(得分:2)
您似乎已经从另一个上下文中检索了该实体,然后将其分配给一个实体,然后将该实体添加到另一个上下文中。然后它会尝试插入Role实体。
您是否从另一个将上下文的生命周期限定为该方法的方法返回Role?
您可能会发现以下链接在更新对象状态方面很有用: Entity states and SaveChanges
答案 1 :(得分:0)
问题在于,当您使用db.Set<MyEntity>.Add
时,您将标记附加到已添加为添加的实体的所有实体。您必须明确标记它们不变:
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
db.Entry(role).State=EntityState.Unchanged;
}
db.SaveChanges();
答案 2 :(得分:0)
我的答案是你不能或至少不应该。
身份验证(角色)和业务(菜单)是应用程序的不同问题。
对我来说,你必须引入ApplicationDb,它是你需要的IdentityDb的一部分,并组织同步。
为了说明我的说法:使用Google或LiveID作为身份验证提供程序进行映像:您能想象从ApplicationDd到Google或Microsoft Dbs的导航属性吗?
显然不是。
因此,创建一个复制身份验证数据库角色的AppRole,并使用应用程序数据库中的此表来构建菜单。
在伪代码中,这看起来像:
List<Int32> l = IdentityContext.GetRolesForUser(currentUserId);
foreach (AppRole role in AppContext.Roles.Where(r => l.Contains(r.Id)))
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
appContext.MenuRoleMaps.Add(mrm);
}
appContext.SaveChanges();
另一种解决方案是对Application和Identity使用相同的上下文。
上下文的继承似乎很好,但我从未测试过它。