EntityType 'IdentityUserLogin' 没有定义键。为此 EntityType 定义键
EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType
我正在使用 Entity Framework Code First 和 MVC 5。当我使用 Individual User Accounts Authentication 创建我的应用程序时,我得到了一个 Account controller 以及它使 Indiv 用户帐户身份验证正常工作所需的所有 classes 和代码。
已经存在的代码是这样的:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
但后来我继续使用代码创建了自己的上下文,所以我现在也有以下内容:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Paintings> Paintings { get; set; }
}
最后,我有以下种子方法来添加一些数据供我在开发时使用:
protected override void Seed(DXContext context)
{
try
{
if (!context.Roles.Any(r => r.Name == "Admin"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = new IdentityRole { Name = "Admin" };
manager.Create(role);
}
context.SaveChanges();
if (!context.Users.Any(u => u.UserName == "James"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { UserName = "James" };
manager.Create(user, "ChangeAsap1@");
manager.AddToRole(user.Id, "Admin");
}
context.SaveChanges();
string userId = "";
userId = context.Users.FirstOrDefault().Id;
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
};
paintings.ForEach(p => context.Paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
我的解决方案构建良好,但是当我尝试访问需要访问数据库的控制器时,出现以下错误:
DX.DOMAIN.Context.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
DX.DOMAIN.Context.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
我做错了什么?是因为我有两个上下文吗?
更新
阅读 Augusto 的回复后,我选择了选项 3。这是我的 DXContext class 现在的样子:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
// remove default initializer
Database.SetInitializer<DXContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Painting> Paintings { get; set; }
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Role>().ToTable("Roles");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
我还添加了一个User.cs
和一个Role.cs
class,它们看起来像这样:
public class User
{
public int Id { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Role
{
public int Id { set; get; }
public string Name { set; get; }
}
我不确定我是否需要用户密码 属性,因为默认的 ApplicationUser 有密码和其他一些字段!
无论如何,上述更改构建正常,但当应用程序为 运行:
时我再次收到此错误
Invalid Column name UserId
UserId
是我 Artist.cs
上的整数 属性
问题是您的ApplicationUser 继承自IdentityUser,其定义如下:
IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }
它们的主键映射在 classIdentityDbContext:
的 OnModelCreating 方法中
modelBuilder.Entity<TUserRole>()
.HasKey(r => new {r.UserId, r.RoleId})
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
.ToTable("AspNetUserLogins");
并且由于您的 DXContext 不是从它派生的,所以这些键没有被定义。
如果你深入Microsoft.AspNet.Identity.EntityFramework
的sources,你就会明白一切。
我前段时间遇到过这种情况,我找到了三种可能的解决方案(也许还有更多):
- 对两个不同的数据库或相同的数据库但不同的 tables 使用单独的 DbContexts。
- 将您的 DXContext 与 ApplicationDbContext 合并并使用一个数据库。
- 对同一个 table 使用单独的 DbContext 并相应地管理它们的迁移。
选项 1:
请参阅底部更新。
选项 2:
您最终会得到一个像这样的 DbContext:
public class DXContext : IdentityDbContext<User, Role,
int, UserLogin, UserRole, UserClaim>//: DbContext
{
public DXContext()
: base("name=DXContext")
{
Database.SetInitializer<DXContext>(null);// Remove default initializer
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static DXContext Create()
{
return new DXContext();
}
//Identity and Authorization
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
// ... your custom DbSets
public DbSet<RoleOperation> RoleOperations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Configure Asp Net Identity Tables
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);
modelBuilder.Entity<Role>().ToTable("Role");
modelBuilder.Entity<UserRole>().ToTable("UserRole");
modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
}
}
选项 3:
您将拥有一个等于选项 2 的 DbContext。我们将其命名为 IdentityContext。你将有另一个 DbContext 称为 DXContext:
public class DXContext : DbContext
{
public DXContext()
: base("name=DXContext") // connection string in the application configuration file.
{
Database.SetInitializer<DXContext>(null); // Remove default initializer
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// Domain Model
public DbSet<User> Users { get; set; }
// ... other custom DbSets
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
modelBuilder.Entity<User>().ToTable("User");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
用户所在的位置:
public class User
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}
使用此解决方案,我将实体 User 映射到与实体 ApplicationUser 相同的table。
然后,使用代码优先迁移,您需要为 IdentityContext 生成迁移,并为 DXContext 生成 THEN,在此之后来自 Shailendra Chauhan 的 post 很棒:Code First Migrations with Multiple Data Contexts
您必须修改为 DXContext 生成的迁移。像这样取决于 ApplicationUser 和 User 之间共享哪些属性:
//CreateTable(
// "dbo.User",
// c => new
// {
// Id = c.Int(nullable: false, identity: true),
// Name = c.String(nullable: false, maxLength: 100),
// SomeOtherColumn = c.String(nullable: false, maxLength: 128),
// })
// .PrimaryKey(t => t.Id);
AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));
然后 运行 使用此自定义 class:
从 global.asax 或应用程序的任何其他位置按顺序进行迁移(首先是身份迁移)
public static class DXDatabaseMigrator
{
public static string ExecuteMigrations()
{
return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
ExecuteDXMigrations());
}
private static string ExecuteIdentityMigrations()
{
IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
return RunMigrations(configuration);
}
private static string ExecuteDXMigrations()
{
DXMigrationConfiguration configuration = new DXMigrationConfiguration();
return RunMigrations(configuration);
}
private static string RunMigrations(DbMigrationsConfiguration configuration)
{
List<string> pendingMigrations;
try
{
DbMigrator migrator = new DbMigrator(configuration);
pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed
if (pendingMigrations.Any())
migrator.Update();
}
catch (Exception e)
{
ExceptionManager.LogException(e);
return e.Message;
}
return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
}
}
这样,我的 n 层横切实体不会最终从 AspNetIdentity classes 继承,因此我不必在我使用它们的每个项目中导入这个框架。
抱歉内容太长post。我希望它能对此提供一些指导。我已经在生产环境中使用了选项 2 和 3。
更新:扩展选项 1
对于最后两个项目,我使用了第一个选项:有一个派生自 IdentityUser 的 AspNetUser class,以及一个名为 AppUser 的单独自定义 class。在我的例子中,DbContexts 分别是 IdentityContext 和 DomainContext。我这样定义了 AppUser 的 Id:
public class AppUser : TrackableEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
// This Id is equal to the Id in the AspNetUser table and it's manually set.
public override int Id { get; set; }
(TrackableEntity 是我在 DomainContext 上下文的重写 SaveChanges 方法中使用的自定义抽象基础 class)
我首先创建 AspNetUser,然后创建 AppUser。这种方法的缺点是您已确保您的“CreateUser”功能是事务性的(请记住,将有两个 DbContext 分别调用 SaveChanges)。由于某些原因,使用 TransactionScope 对我不起作用,所以我最终做了一些丑陋但对我有用的事情:
IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);
if (!identityResult.Succeeded)
throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));
AppUser appUser;
try
{
appUser = RegisterInAppUserTable(model, aspNetUser);
}
catch (Exception)
{
// Roll back
UserManager.Delete(aspNetUser);
throw;
}
(拜托,如果有人提出了更好的方法来完成这部分,我很感激评论或建议对此答案进行编辑)
好处是您不必修改迁移,并且您可以在 AppUser 上使用任何疯狂的继承层次结构,而不会弄乱 AspNetUser。实际上,我对我的 IdentityContext(从 IdentityDbContext 派生的上下文)使用自动迁移:
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(IdentityContext context)
{
}
}
这种方法还有一个好处,就是避免让您的 n 层横切实体继承自 AspNetIdentity classes。
对于使用 ASP.NET Identity 2.1 并将主键从默认 string
更改为 int
或 Guid
的用户,如果您仍然
EntityType 'xxxxUserLogin' has no key defined. Define the key for this EntityType.
EntityType 'xxxxUserRole' has no key defined. Define the key for this EntityType.
您可能只是忘记在 IdentityDbContext
上指定新的密钥类型:
public class AppIdentityDbContext : IdentityDbContext<
AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityDbContext()
: base("MY_CONNECTION_STRING")
{
}
......
}
如果你有
public class AppIdentityDbContext : IdentityDbContext
{
......
}
甚至
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
......
}
当您尝试添加迁移或更新数据库时,您会遇到 'no key defined' 错误。
在我的例子中,我正确地继承了 IdentityDbContext(定义了我自己的自定义类型和键),但无意中删除了对基础 class 的 OnModelCreating 的调用:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // I had removed this
/// Rest of on model creating here.
}
然后从身份 classes 中修复了我丢失的索引,然后我可以生成迁移并适当地启用迁移。
通过如下更改 DbContext;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
只需将 OnModelCreating
方法调用添加到 base.OnModelCreating(modelBuilder);它变得很好。我正在使用 EF6。
特别感谢#The Senator
我的问题很相似 - 我有一个新的 table 我正在创建它以绑定到身份用户。看完上面的答案后,意识到它与 IsdentityUser 和继承的属性有关。我已经将 Identity 设置为它自己的上下文,因此为了避免将两者固有地联系在一起,而不是将相关用户 table 用作真正的 EF 属性,我设置了一个非映射 属性 使用查询获取相关实体。 (DataManager 设置为检索 OtherEntity 所在的当前上下文。)
[Table("UserOtherEntity")]
public partial class UserOtherEntity
{
public Guid UserOtherEntityId { get; set; }
[Required]
[StringLength(128)]
public string UserId { get; set; }
[Required]
public Guid OtherEntityId { get; set; }
public virtual OtherEntity OtherEntity { get; set; }
}
public partial class UserOtherEntity : DataManager
{
public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
{
return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
}
}
public partial class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[NotMapped]
public IEnumerable<OtherEntity> OtherEntities
{
get
{
return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
}
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
// relationship.DeleteBehavior = DeleteBehavior.Restrict;
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
}
}
我正在使用 Entity Framework Code First 和 MVC 5。当我使用 Individual User Accounts Authentication 创建我的应用程序时,我得到了一个 Account controller 以及它使 Indiv 用户帐户身份验证正常工作所需的所有 classes 和代码。
已经存在的代码是这样的:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
但后来我继续使用代码创建了自己的上下文,所以我现在也有以下内容:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Paintings> Paintings { get; set; }
}
最后,我有以下种子方法来添加一些数据供我在开发时使用:
protected override void Seed(DXContext context)
{
try
{
if (!context.Roles.Any(r => r.Name == "Admin"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = new IdentityRole { Name = "Admin" };
manager.Create(role);
}
context.SaveChanges();
if (!context.Users.Any(u => u.UserName == "James"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { UserName = "James" };
manager.Create(user, "ChangeAsap1@");
manager.AddToRole(user.Id, "Admin");
}
context.SaveChanges();
string userId = "";
userId = context.Users.FirstOrDefault().Id;
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
};
paintings.ForEach(p => context.Paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
我的解决方案构建良好,但是当我尝试访问需要访问数据库的控制器时,出现以下错误:
DX.DOMAIN.Context.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
DX.DOMAIN.Context.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
我做错了什么?是因为我有两个上下文吗?
更新
阅读 Augusto 的回复后,我选择了选项 3。这是我的 DXContext class 现在的样子:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
// remove default initializer
Database.SetInitializer<DXContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Painting> Paintings { get; set; }
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Role>().ToTable("Roles");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
我还添加了一个User.cs
和一个Role.cs
class,它们看起来像这样:
public class User
{
public int Id { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Role
{
public int Id { set; get; }
public string Name { set; get; }
}
我不确定我是否需要用户密码 属性,因为默认的 ApplicationUser 有密码和其他一些字段!
无论如何,上述更改构建正常,但当应用程序为 运行:
时我再次收到此错误Invalid Column name UserId
UserId
是我 Artist.cs
问题是您的ApplicationUser 继承自IdentityUser,其定义如下:
IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }
它们的主键映射在 classIdentityDbContext:
的 OnModelCreating 方法中modelBuilder.Entity<TUserRole>()
.HasKey(r => new {r.UserId, r.RoleId})
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
.ToTable("AspNetUserLogins");
并且由于您的 DXContext 不是从它派生的,所以这些键没有被定义。
如果你深入Microsoft.AspNet.Identity.EntityFramework
的sources,你就会明白一切。
我前段时间遇到过这种情况,我找到了三种可能的解决方案(也许还有更多):
- 对两个不同的数据库或相同的数据库但不同的 tables 使用单独的 DbContexts。
- 将您的 DXContext 与 ApplicationDbContext 合并并使用一个数据库。
- 对同一个 table 使用单独的 DbContext 并相应地管理它们的迁移。
选项 1: 请参阅底部更新。
选项 2: 您最终会得到一个像这样的 DbContext:
public class DXContext : IdentityDbContext<User, Role,
int, UserLogin, UserRole, UserClaim>//: DbContext
{
public DXContext()
: base("name=DXContext")
{
Database.SetInitializer<DXContext>(null);// Remove default initializer
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static DXContext Create()
{
return new DXContext();
}
//Identity and Authorization
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
// ... your custom DbSets
public DbSet<RoleOperation> RoleOperations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Configure Asp Net Identity Tables
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);
modelBuilder.Entity<Role>().ToTable("Role");
modelBuilder.Entity<UserRole>().ToTable("UserRole");
modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
}
}
选项 3: 您将拥有一个等于选项 2 的 DbContext。我们将其命名为 IdentityContext。你将有另一个 DbContext 称为 DXContext:
public class DXContext : DbContext
{
public DXContext()
: base("name=DXContext") // connection string in the application configuration file.
{
Database.SetInitializer<DXContext>(null); // Remove default initializer
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// Domain Model
public DbSet<User> Users { get; set; }
// ... other custom DbSets
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
modelBuilder.Entity<User>().ToTable("User");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
用户所在的位置:
public class User
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}
使用此解决方案,我将实体 User 映射到与实体 ApplicationUser 相同的table。
然后,使用代码优先迁移,您需要为 IdentityContext 生成迁移,并为 DXContext 生成 THEN,在此之后来自 Shailendra Chauhan 的 post 很棒:Code First Migrations with Multiple Data Contexts
您必须修改为 DXContext 生成的迁移。像这样取决于 ApplicationUser 和 User 之间共享哪些属性:
//CreateTable(
// "dbo.User",
// c => new
// {
// Id = c.Int(nullable: false, identity: true),
// Name = c.String(nullable: false, maxLength: 100),
// SomeOtherColumn = c.String(nullable: false, maxLength: 128),
// })
// .PrimaryKey(t => t.Id);
AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));
然后 运行 使用此自定义 class:
从 global.asax 或应用程序的任何其他位置按顺序进行迁移(首先是身份迁移)public static class DXDatabaseMigrator
{
public static string ExecuteMigrations()
{
return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
ExecuteDXMigrations());
}
private static string ExecuteIdentityMigrations()
{
IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
return RunMigrations(configuration);
}
private static string ExecuteDXMigrations()
{
DXMigrationConfiguration configuration = new DXMigrationConfiguration();
return RunMigrations(configuration);
}
private static string RunMigrations(DbMigrationsConfiguration configuration)
{
List<string> pendingMigrations;
try
{
DbMigrator migrator = new DbMigrator(configuration);
pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed
if (pendingMigrations.Any())
migrator.Update();
}
catch (Exception e)
{
ExceptionManager.LogException(e);
return e.Message;
}
return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
}
}
这样,我的 n 层横切实体不会最终从 AspNetIdentity classes 继承,因此我不必在我使用它们的每个项目中导入这个框架。
抱歉内容太长post。我希望它能对此提供一些指导。我已经在生产环境中使用了选项 2 和 3。
更新:扩展选项 1
对于最后两个项目,我使用了第一个选项:有一个派生自 IdentityUser 的 AspNetUser class,以及一个名为 AppUser 的单独自定义 class。在我的例子中,DbContexts 分别是 IdentityContext 和 DomainContext。我这样定义了 AppUser 的 Id:
public class AppUser : TrackableEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
// This Id is equal to the Id in the AspNetUser table and it's manually set.
public override int Id { get; set; }
(TrackableEntity 是我在 DomainContext 上下文的重写 SaveChanges 方法中使用的自定义抽象基础 class)
我首先创建 AspNetUser,然后创建 AppUser。这种方法的缺点是您已确保您的“CreateUser”功能是事务性的(请记住,将有两个 DbContext 分别调用 SaveChanges)。由于某些原因,使用 TransactionScope 对我不起作用,所以我最终做了一些丑陋但对我有用的事情:
IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);
if (!identityResult.Succeeded)
throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));
AppUser appUser;
try
{
appUser = RegisterInAppUserTable(model, aspNetUser);
}
catch (Exception)
{
// Roll back
UserManager.Delete(aspNetUser);
throw;
}
(拜托,如果有人提出了更好的方法来完成这部分,我很感激评论或建议对此答案进行编辑)
好处是您不必修改迁移,并且您可以在 AppUser 上使用任何疯狂的继承层次结构,而不会弄乱 AspNetUser。实际上,我对我的 IdentityContext(从 IdentityDbContext 派生的上下文)使用自动迁移:
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(IdentityContext context)
{
}
}
这种方法还有一个好处,就是避免让您的 n 层横切实体继承自 AspNetIdentity classes。
对于使用 ASP.NET Identity 2.1 并将主键从默认 string
更改为 int
或 Guid
的用户,如果您仍然
EntityType 'xxxxUserLogin' has no key defined. Define the key for this EntityType.
EntityType 'xxxxUserRole' has no key defined. Define the key for this EntityType.
您可能只是忘记在 IdentityDbContext
上指定新的密钥类型:
public class AppIdentityDbContext : IdentityDbContext<
AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityDbContext()
: base("MY_CONNECTION_STRING")
{
}
......
}
如果你有
public class AppIdentityDbContext : IdentityDbContext
{
......
}
甚至
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
......
}
当您尝试添加迁移或更新数据库时,您会遇到 'no key defined' 错误。
在我的例子中,我正确地继承了 IdentityDbContext(定义了我自己的自定义类型和键),但无意中删除了对基础 class 的 OnModelCreating 的调用:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // I had removed this
/// Rest of on model creating here.
}
然后从身份 classes 中修复了我丢失的索引,然后我可以生成迁移并适当地启用迁移。
通过如下更改 DbContext;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
只需将 OnModelCreating
方法调用添加到 base.OnModelCreating(modelBuilder);它变得很好。我正在使用 EF6。
特别感谢#The Senator
我的问题很相似 - 我有一个新的 table 我正在创建它以绑定到身份用户。看完上面的答案后,意识到它与 IsdentityUser 和继承的属性有关。我已经将 Identity 设置为它自己的上下文,因此为了避免将两者固有地联系在一起,而不是将相关用户 table 用作真正的 EF 属性,我设置了一个非映射 属性 使用查询获取相关实体。 (DataManager 设置为检索 OtherEntity 所在的当前上下文。)
[Table("UserOtherEntity")]
public partial class UserOtherEntity
{
public Guid UserOtherEntityId { get; set; }
[Required]
[StringLength(128)]
public string UserId { get; set; }
[Required]
public Guid OtherEntityId { get; set; }
public virtual OtherEntity OtherEntity { get; set; }
}
public partial class UserOtherEntity : DataManager
{
public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
{
return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
}
}
public partial class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[NotMapped]
public IEnumerable<OtherEntity> OtherEntities
{
get
{
return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
}
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
// relationship.DeleteBehavior = DeleteBehavior.Restrict;
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
}
}