EntityType“IdentityUserLogin”没有定义密钥。 定义这个EntityType的关键
我正在使用Entity Framework Code First和MVC 5.当我使用个人用户帐户身份validation创build了我的应用程序时,我获得了一个帐户控制器,并且还提供了使个人用户帐户身份validation正常工作所需的所有必需类和代码。
已经有的代码是这样的:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false) { } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }
但是,我继续使用代码先创build自己的上下文,现在我也有以下几点:
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 = "160wpb4.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); } } } }
我的解决scheme构build良好,但是当我尝试访问需要访问数据库的控制器时,出现以下错误:
DX.DOMAIN.Context.IdentityUserLogin :: EntityType“IdentityUserLogin”没有定义键。 定义这个EntityType的关键。
DX.DOMAIN.Context.IdentityUserRole :: EntityType“IdentityUserRole”没有定义键。 定义这个EntityType的关键。
我究竟做错了什么? 是因为我有两个上下文吗?
UPDATE
在阅读奥古斯托的答复后,我select了scheme3 。 下面是我的DXContext类现在的样子:
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
类,他们看起来像这样:
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有这个和其他一些字段!
无论如何,上述更改生成的罚款,但我又遇到这个错误,当应用程序运行:
无效的列名称UserId
UserId
是我的Artist.cs
上的整数属性
问题是你的ApplicationUserinheritance自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; }
并且它们的主键映射在IdentityDbContext类的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
的来源 ,您将会了解一切。
我前段时间遇到过这种情况,我find了三种可能的解决scheme(可能还有更多):
- 针对两个不同的数据库或相同的数据库使用单独的DbContexts,但使用不同的表。
- 合并您的DXContext与ApplicationDbContext并使用一个数据库。
- 针对同一个表使用单独的DbContext,并相应地pipe理其迁移。
选项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:您将有一个DbContext等于选项2.让我们将其命名为IdentityContext。 而且你将会有另一个叫做DXContext的DbContext:
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; } }
通过这个解决scheme,我将实体用户映射到与实体ApplicationUser相同的表。
然后,使用Code First Migrations,您将需要为这个DXContext生成IdentityContext和THEN的迁移,在Shailendra Chauhan撰写这篇伟大的文章之后: 使用多个数据上下文进行代码优先迁移
您将不得不修改为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));
然后使用此自定义类从global.asax或应用程序的任何其他位置按顺序(首先是Identity迁移)运行迁移:
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类inheritance,因此我不必在每个使用它的项目中都导入这个框架。
很抱歉,广泛的职位。 我希望能就此提供一些指导。 我已经在生产环境中使用了选项2和3。
更新:展开选项1
对于最后两个项目,我使用了第一个选项:具有派生自IdentityUser的AspNetUser类和名为AppUser的单独自定义类。 在我的情况下,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方法中使用的自定义抽象基类)
我首先创buildAspNetUser,然后创buildAppUser。 这种方法的缺点是你确保你的“CreateUser”function是事务性的(记住将有两个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; }
(请,如果有人有更好的方式做这个部分我欣赏评论或build议编辑这个答案)
好处是你不需要修改迁移,你可以在AppUser上使用任何疯狂的inheritance层次结构,而不会干扰AspNetUser 。 实际上,我使用自动迁移为我的IdentityContext(从IdentityDbContext派生的上下文):
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext> { public IdentityMigrationConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = false; } protected override void Seed(IdentityContext context) { } }
这种方法也有避免让你的n层交叉实体从AspNetIdentity类inheritance的好处。
在我的情况下,我正确地inheritance了IdentityDbContext(使用我自己的自定义types和键定义),但无意中删除了对基类的OnModelCreating的调用:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // I had removed this /// Rest of on model creating here. }
然后,从身份类中修复缺less的索引,然后我可以生成迁移并适当地启用迁移。
对于那些使用ASP.NET Identity 2.1并将主键从默认string
更改为int
或Guid
,如果还有问题的话
EntityType'xxxxUserLogin'没有定义密钥。 定义这个EntityType的关键。
EntityType'xxxxUserRole'没有定义键。 定义这个EntityType的关键。
你可能忘了在IdentityDbContext
上指定新的键types:
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> { ...... }
当您尝试添加迁移或更新数据库时,您将得到“没有键定义”的错误。
通过更改DbContext,如下所示;
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); }
只需在OnModelCreating方法调用中添加base.OnModelCreating(modelBuilder); 它会好起来的 我正在使用EF6。
特别感谢#参议员
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"); } }
我的问题是类似的 – 我有一个新的桌子,我创build这个ahd以配合身份用户。 读完上面的答案后,意识到它与IsdentityUser和inheritance的特性有关。 我已经将Identity设置为自己的Context,所以为了避免将两者固定在一起,而不是使用相关的用户表作为真正的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); } } }