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


105

我正在使用Entity Framework Code First和MVC5。使用个人用户帐户身份验证创建我的应用程序时,我被赋予了一个帐户控制器,并与之一起获得Indiv用户帐户身份验证才能正常工作的所有必需的类和代码。 。

其中已经存在的代码如下:

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'没有定义键。定义此EntityType的键。

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

我究竟做错了什么?是因为我有两种情况吗?

更新

阅读奥古斯托的回复后,我选择了选项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();
    }
}

我还添加了一个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

Answers:


116

问题是您的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; }

及其主键映射到类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,你就会明白一切。

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

  1. 对两个不同的数据库或相同的数据库但使用不同的表使用单独的DbContext。
  2. 将DXContext与ApplicationDbContext合并并使用一个数据库。
  3. 对同一个表使用单独的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。您将拥有另一个名为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; }
}

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

然后,遵循Shailendra Chauhan的这篇精彩文章:使用代码优先迁移,为IdentityContext和THEN为DXContext 产生迁移:具有多个数据上下文的代码优先迁移

您必须修改为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类,因此,我不必在使用它们的每个项目中都导入此框架。

对不起,我的帖子太多了。我希望它可以对此提供一些指导。我已经在生产环境中使用了选项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方法中使用)

我首先创建AspNetUser,然后创建AppUser。这种方法的缺点是必须确保“ CreateUser”功能具有事务性(请记住,将有两个分别调用SaveChanges的DbContext)。由于某些原因,使用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类的好处。


感谢@Augusto提供了广泛的帖子。是否需要使用迁移才能使选项3正常工作?据我所知,EF迁移用于回滚更改吗?如果要删除数据库,然后重新创建数据库并将其植入每个新版本中,是否需要所有这些迁移工作?
J86

我没有使用迁移就没有尝试过。我不知道您是否可以不使用它们就完成此任务。也许有可能。我总是不得不使用迁移来保留插入到数据库中的所有自定义数据。
奥古斯托·巴雷托

要指出的一件事是,如果您确实使用了Migrations ...,则应该使用AddOrUpdate(new EntityObject { shoes = green})也称为“ upsert”的文件。与仅添加到上下文相反,否则您将只创建重复/冗余实体上下文信息。
Chef_Code

我想使用第三个选项,但我有点不明白。有人可以告诉我IdentityContext的外观如何吗?因为它不可能完全像选项2一样!你能帮我@AugustoBarreto吗?我在类似问题上有所作为,也许您可​​以在那儿帮助我
Arianit

您的“ TrackableEntity”是什么样的?
Ciaran Gallagher

224

就我而言,我正确地继承了IdentityDbContext(定义了我自己的自定义类型和键),但无意中删除了对基类的OnModelCreating的调用:

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

然后从身份类修复我丢失的索引,然后我可以生成迁移并适当地启用迁移。


曾有同样的问题“免排队”。您的解决方案有效。:) ty。
开发人员MariusŽilėnas2016年

2
这解决了我的问题,我不得不重写OnModelCreating方法,以使用使用fluent api的自定义Mapping处理复杂的实体关系。事实证明,由于我使用与身份相同的上下文,因此在声明映射之前,我忘记在答案中添加一行。干杯。
丹丹

如果没有“ Override void OnModelCreating”,它会起作用,但是如果您覆盖,则需要添加“ base.OnModelCreating(modelBuilder);”。覆盖。解决了我的问题。

13

对于那些使用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")
    {
    }
    ......
}

如果你有

public class AppIdentityDbContext : IdentityDbContext
{
    ......
}

甚至

public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    ......
}

当您尝试添加迁移或更新数据库时,将收到“未定义键”错误。


我也试图将ID更改为Int并遇到此问题,但是我更改了DbContext以指定新的密钥类型。我还有其他地方应该检查吗?我以为我很认真地遵循说明。
凯尔(Kyle)

1
@Kyle:您是否正在尝试将所有实体的ID更改为int,即AppRole,AppUser,AppUserClaim,AppUserLogin和AppUserRole?如果是这样,您可能还需要确保为这些类指定了新的键类型。就像“公共类AppUserLogin:IdentityUserLogin <int> {}”
David Liang

1
这是有关自定义主键数据类型的官方文档:docs.microsoft.com/en-us/aspnet/core/security/authentication/…–
AdrienTorris

1
是的,我的问题是,我从常规DbContext类继承,而不是从IdentityDbContext <AppUser>继承。谢谢,这
很有帮助

13

通过如下更改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


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

        }
    }

0

我的问题很相似-我创建了一个新表,用于绑定身份用户。阅读以上答案后,意识到它与IsdentityUser和继承的属性有关。我已经将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);
            }
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.