EntityType 'IdentityUserLogin' 没有定义键.定义此 EntityType 的键 [英] EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType

查看:24
本文介绍了EntityType 'IdentityUserLogin' 没有定义键.定义此 EntityType 的键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am working with Entity Framework Code First and MVC 5. When I created my application with Individual User Accounts Authentication I was given an Account controller and along with it all the required classes and code that is needed to get the Indiv User Accounts authentication to work.

已经存在的代码是这样的:

Among the code already in place was this:

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

    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

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

But then I went ahead and created my own context using code first, so I now have the following too:

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

最后,我有以下种子方法来添加一些数据供我在开发时使用:

Finally I have the following seed method to add some data for me to work with whilst developing:

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

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

My solution builds fine, but when I try and access a controller that requires access to the database I get the following error:

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

DX.DOMAIN.Context.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.

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

DX.DOMAIN.Context.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.

我做错了什么?是不是因为我有两个上下文?

What am I doing wrong? Is it because I have two contexts?

更新

阅读 Augusto 的回复后,我选择了选项 3.这是我的 DXContext 类现在的样子:

After reading Augusto's reply, I went with Option 3. Here is what my DXContext class looks like now:

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

I also added a User.cs and a Role.cs class, they look like this:

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 具有该属性和一堆其他字段!

I wasn't sure if I would need a password property on the user, since the default ApplicationUser has that and a bunch of other fields!

无论如何,上述更改构建良好,但在运行应用程序时我再次收到此错误:

Anyways, the above change builds fine, but again I get this error when the application is ran:

无效的列名用户 ID

UserId 是我的 Artist.cs

推荐答案

问题是你的ApplicationUser继承自IdentityUser,定义如下:

The problem is that your ApplicationUser inherits from IdentityUser, which is defined like this:

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 中:

and their primary keys are mapped in the method OnModelCreating of the class IdentityDbContext:

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 不是从它派生的,所以这些键没有被定义.

and as your DXContext doesn't derive from it, those keys don't get defined.

如果您深入了解 sources Microsoft.AspNet.Identity.EntityFramework,你会明白一切.

If you dig into the sources of Microsoft.AspNet.Identity.EntityFramework, you will understand everything.

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

I came across this situation some time ago, and I found three possible solutions (maybe there are more):

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

选项 1:见更新底部.

选项 2:你最终会得到一个这样的 DbContext:

Option 2: You will end up with a DbContext like this one:

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.您将拥有另一个名为 DXContext 的 DbContext:

Option 3: You will have one DbContext equal to the option 2. Let's name it IdentityContext. And you will have another DbContext called 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 相同的表.

With this solution, I'm mapping the entity User to the same table as the entity ApplicationUser.

然后,使用 Code First 迁移,您需要为 IdentityContext 生成迁移,并按照 Shailendra Chauhan 的这篇精彩博文为 DXContext 生成THEN:代码具有多个数据上下文的第一次迁移

Then, using Code First Migrations you'll need to generate the migrations for the IdentityContext and THEN for the DXContext, following this great post from Shailendra Chauhan: Code First Migrations with Multiple Data Contexts

您必须修改为 DXContext 生成的迁移.类似这样的事情取决于 ApplicationUser 和 User 之间共享哪些属性:

You'll have to modify the migration generated for DXContext. Something like this depending on which properties are shared between ApplicationUser and 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 或使用此自定义类的应用程序的任何其他位置按顺序运行迁移(首先是身份迁移):

and then running the migrations in order (first the Identity migrations) from the global.asax or any other place of your application using this custom class:

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 类继承,因此我不必在我使用它们的每个项目中都导入这个框架.

This way, my n-tier cross-cutting entities don't end up inheriting from AspNetIdentity classes, and therefore I don't have to import this framework in every project where I use them.

抱歉发布了大量帖子.我希望它可以在这方面提供一些指导.我已经在生产环境中使用了选项 2 和 3.

Sorry for the extensive post. I hope it could offer some guidance on this. I have already used options 2 and 3 in production environments.

更新:展开选项 1

对于最后两个项目,我使用了第一个选项:拥有一个从 IdentityUser 派生的 AspNetUser 类,以及一个名为 AppUser 的单独自定义类.就我而言,DbContext 分别是 IdentityContext 和 DomainContext.我像这样定义了 AppUser 的 ID:

For the last two projects I have used the 1st option: having an AspNetUser class that derives from IdentityUser, and a separate custom class called AppUser. In my case, the DbContexts are IdentityContext and DomainContext respectively. And I defined the Id of the AppUser like this:

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 方法中使用的自定义抽象基类)

(TrackableEntity is the custom abstract base class that I use in the overridden SaveChanges method of my DomainContext context)

我首先创建 AspNetUser,然后创建 AppUser.这种方法的缺点是您已确保您的CreateUser"功能是事务性的(请记住,将有两个 DbContexts 分别调用 SaveChanges).由于某种原因,使用 TransactionScope 对我不起作用,所以我最终做了一些丑陋的事情,但这对我有用:

I first create the AspNetUser and then the AppUser. The drawback with this approach is that you have ensured that your "CreateUser" functionality is transactional (remember that there will be two DbContexts calling SaveChanges separately). Using TransactionScope didn't work for me for some reason, so I ended up doing something ugly but that works for me:

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

(请,如果有人提出了更好的方法来做这部分,我很感激对此答案发表评论或提出编辑建议)

(Please, if somebody comes with a better way of doing this part I appreciate commenting or proposing an edit to this answer)

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

The benefits are that you don't have to modify the migrations and you can use any crazy inheritance hierarchy over the AppUser without messing with the AspNetUser. And actually, I use Automatic Migrations for my IdentityContext (the context that derives from IdentityDbContext):

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

    protected override void Seed(IdentityContext context)
    {
    }
}

这种方法还具有避免让您的 n 层横切实体从 AspNetIdentity 类继承的好处.

This approach also has the benefit of avoiding to have your n-tier cross-cutting entities inheriting from AspNetIdentity classes.

这篇关于EntityType 'IdentityUserLogin' 没有定义键.定义此 EntityType 的键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆