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.csclass,它们看起来像这样:

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.EntityFrameworksources,你就会明白一切。

我前段时间遇到过这种情况,我找到了三种可能的解决方案(也许还有更多):

  1. 对两个不同的数据库或相同的数据库但不同的 tables 使用单独的 DbContexts。
  2. 将您的 DXContext 与 ApplicationDbContext 合并并使用一个数据库。
  3. 对同一个 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 更改为 intGuid 的用户,如果您仍然

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

        }
    }