EntityType'IdentityUserLogin'没有定义键.定义此EntityType的键

J86*_*J86 98 seeding ef-code-first asp.net-mvc-5

我正在使用Entity Framework Code First和MVC 5.当我使用个人用户帐户身份验证创建我的应用程序时,我获得了一个帐户控制器以及所有必需的类和代码,以使Indiv用户帐户身份验证工作.

已经存在的代码包括:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
    {

    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}
Run Code Online (Sandbox Code Playgroud)

但后来我继续使用代码创建了我自己的上下文,所以我现在也有以下内容:

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; }        
}
Run Code Online (Sandbox Code Playgroud)

最后,我有以下种子方法为我添加一些数据,同时开发:

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

}
Run Code Online (Sandbox Code Playgroud)

我的解决方案构建正常,但当我尝试访问需要访问数据库的控制器时,我收到以下错误:

DX.DOMAIN.Context.IdentityUserLogin :: EntityType'IdentityUserLogin'没有定义键.定义此EntityType的键.

DX.DOMAIN.Context.IdentityUserRole :: EntityType'IdentityUserRole'没有定义键.定义此EntityType的键.

我究竟做错了什么?是因为我有两个背景吗?

UPDATE

在阅读Augusto的回复后,我选择了备选方案3.这是我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我还添加了一个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; }
}
Run Code Online (Sandbox Code Playgroud)

我不确定我是否需要用户的密码属性,因为默认的ApplicationUser具有该属性和一堆其他字段!

无论如何,上面的更改构建正常,但我再次运行应用程序时出现此错误:

列名称UserId无效

UserId 是我的整数属性 Artist.cs

The*_*tor 214

在我来说,我已经从IdentityDbContext正确继承(用我自己的自定义类型和定义的键),但已经无意中删除调用基类的OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder); // I had removed this
    /// Rest of on model creating here.
}
Run Code Online (Sandbox Code Playgroud)

然后,我从标识类中修复了丢失的索引,然后我可以生成迁移并适当地启用迁移.

  • 这解决了我的问题,我必须覆盖OnModelCreating方法,以便为复杂的实体关系使用流畅的api包含自定义映射.事实证明我忘记在声明我的映射之前在答案中添加行,因为我使用与Identity相同的上下文.干杯. (2认同)

Aug*_*eto 110

问题是您的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; }
Run Code Online (Sandbox Code Playgroud)

并且它们的主键映射在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");
Run Code Online (Sandbox Code Playgroud)

并且由于您的DXContext不是从它派生的,因此这些键不会被定义.

如果你深入到Microsoft.AspNet.Identity.EntityFramework,你就会明白一切.

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

  1. 对两个不同的数据库或相同的数据库但不同的表使用单独的DbContexts.
  2. 将DXContext与ApplicationDbContext合并,并使用一个数据库.
  3. 对同一个表使用单独的DbContexts并相应地管理它们的迁移.

选项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);
    }
}
Run Code Online (Sandbox Code Playgroud)

选项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();
    }
}
Run Code Online (Sandbox Code Playgroud)

用户在哪里:

public class User
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }

    [Required, StringLength(128)]
    public string SomeOtherColumn { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用此解决方案,我将实体User映射到与实体ApplicationUser相同的表.

然后,使用Code First Migrations,你需要为Shanelndra Chauhan撰写的这篇伟大帖子生成IdentityContext和THEN for DXContext的迁移: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));
Run Code Online (Sandbox Code Playgroud)

然后使用此自定义类从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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,我的n层交叉实体最终不会继承AspNetIdentity类,因此我不必在我使用它们的每个项目中导入该框架.

对不起,广泛的帖子.我希望它能为此提供一些指导.我已经在生产环境中使用了选项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; }
Run Code Online (Sandbox Code Playgroud)

(TrackableEntity是我在DomainContext上下文的重写的SaveChanges方法中使用的自定义抽象基类)

我首先创建AspNetUser,然后创建AppUser.这种方法的缺点是你必须确保你的"CreateUser"功能是事务性的(请记住,将有两个DbContexts单独调用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;
        }
Run Code Online (Sandbox Code Playgroud)

(请,如果有人有更好的方式来做这个部分,我感谢评论或建议编辑这个答案)

好处是您不必修改迁移,并且可以在AppUser上使用任何疯狂的继承层次结构,而不会弄乱AspNetUser.实际上,我为IdentityContext(从IdentityDbContext派生的上下文)使用自动迁移:

public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
    public IdentityMigrationConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = false;
    }

    protected override void Seed(IdentityContext context)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法还有一个好处,即避免让您的n层交叉实体继承AspNetIdentity类.


Dav*_*ang 12

对于那些使用ASP.NET Identity 2.1并且已将主键从默认值更改string为其中一个int或者Guid,如果您仍在使用的话

EntityType'xxxxUserLogin'没有定义键.定义此EntityType的键.

EntityType'xxxxUserRole'没有定义键.定义此EntityType的键.

你可能只是忘了指定新的密钥类型IdentityDbContext:

public class AppIdentityDbContext : IdentityDbContext<
    AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppIdentityDbContext()
        : base("MY_CONNECTION_STRING")
    {
    }
    ......
}
Run Code Online (Sandbox Code Playgroud)

如果你有

public class AppIdentityDbContext : IdentityDbContext
{
    ......
}
Run Code Online (Sandbox Code Playgroud)

甚至

public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    ......
}
Run Code Online (Sandbox Code Playgroud)

当您尝试添加迁移或更新数据库时,您将得到"无键定义"错误.


小智 12

通过将DbContext更改为如下;

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    }
Run Code Online (Sandbox Code Playgroud)

只需在OnModelCreating方法中添加对base.OnModelCreating(modelBuilder)的调用; 它变得很好.我正在使用EF6.

特别感谢#The Senator