EntityType ‘IdentityUserLogin’ não possui chave definida. Defina a chave para este EntityType

Estou trabalhando com o Entity Framework Code First e MVC 5. Quando criei meu aplicativo com Autenticação de Contas de Usuários Individuais, recebi um controlador Account e, junto com ele, todas as classs e códigos necessários para que a autenticação de Contas de Usuário Indiv funcionasse. .

Entre o código já em vigor foi este:

public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false) { } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } } 

Mas então eu fui em frente e criei meu próprio contexto usando o código primeiro, então agora também tenho o seguinte:

 public class DXContext : DbContext { public DXContext() : base("DXContext") { } public DbSet Users { get; set; } public DbSet Roles { get; set; } public DbSet Artists { get; set; } public DbSet Paintings { get; set; } } 

Finalmente eu tenho o seguinte método de semente para adicionar alguns dados para eu trabalhar enquanto desenvolvo:

 protected override void Seed(DXContext context) { try { if (!context.Roles.Any(r => r.Name == "Admin")) { var store = new RoleStore(context); var manager = new RoleManager(store); var role = new IdentityRole { Name = "Admin" }; manager.Create(role); } context.SaveChanges(); if (!context.Users.Any(u => u.UserName == "James")) { var store = new UserStore(context); var manager = new UserManager(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 { new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://sofpt.miximages.com/ef-code-first/404.gif", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId }, }; artists.ForEach(a => context.Artists.Add(a)); context.SaveChanges(); var paintings = new List { new Painting { Title = "The Persistence of Memory", ImgUrl = "http://sofpt.miximages.com/ef-code-first/404.gif", 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); } } } } 

Minha solução constrói bem, mas quando tento acessar um controlador que requer access ao database, recebo o seguinte erro:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType ‘IdentityUserLogin’ não possui chave definida. Defina a chave para este EntityType.

DX.DOMAIN.Context.IdentityUserRole:: EntityType ‘IdentityUserRole’ não possui chave definida. Defina a chave para este EntityType.

O que estou fazendo de errado? É porque eu tenho dois contextos?

ATUALIZAR

Depois de ler a resposta de Augusto, fui com a Opção 3 . Aqui está a aparência da minha class DXContext agora:

 public class DXContext : DbContext { public DXContext() : base("DXContext") { // remove default initializer Database.SetInitializer(null); Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; } public DbSet Users { get; set; } public DbSet Roles { get; set; } public DbSet Artists { get; set; } public DbSet Paintings { get; set; } public static DXContext Create() { return new DXContext(); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity().ToTable("Users"); modelBuilder.Entity().ToTable("Roles"); } public DbQuery Query() where T : class { return Set().AsNoTracking(); } } 

Eu também adicionei um User.cs e uma class Role.cs , eles se parecem com isso:

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

Eu não tinha certeza se precisaria de uma propriedade de senha no usuário, já que o ApplicationUser padrão tem isso e um monte de outros campos!

De qualquer forma, a alteração acima é boa, mas novamente recebo esse erro quando o aplicativo é executado:

Nome da Coluna Inválido UserId

UserId é uma propriedade inteira no meu Artist.cs

O problema é que seu ApplicationUser é herdado do IdentityUser , que é definido assim:

 IdentityUser : IdentityUser, IUser .... public virtual ICollection Roles { get; private set; } public virtual ICollection Claims { get; private set; } public virtual ICollection Logins { get; private set; } 

e suas chaves primárias são mapeadas no método OnModelCreating da class IdentityDbContext :

 modelBuilder.Entity() .HasKey(r => new {r.UserId, r.RoleId}) .ToTable("AspNetUserRoles"); modelBuilder.Entity() .HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId}) .ToTable("AspNetUserLogins"); 

e como o seu DXContext não deriva dele, essas chaves não são definidas.

Se você cavar as fonts do Microsoft.AspNet.Identity.EntityFramework , você entenderá tudo.

Eu me deparei com essa situação há algum tempo, e encontrei três soluções possíveis (talvez existam mais):

  1. Use DbContexts separados em dois bancos de dados diferentes ou no mesmo database, mas em tabelas diferentes.
  2. Mesclar seu DXContext com ApplicationDbContext e usar um database.
  3. Use DbContexts separados na mesma tabela e gerencie suas migrações de acordo.

Opção 1: consulte a atualização na parte inferior.

Opção 2: você terminará com um DbContext como este:

 public class DXContext : IdentityDbContext//: DbContext { public DXContext() : base("name=DXContext") { Database.SetInitializer(null);// Remove default initializer Configuration.ProxyCreationEnabled = false; Configuration.LazyLoadingEnabled = false; } public static DXContext Create() { return new DXContext(); } //Identity and Authorization public DbSet UserLogins { get; set; } public DbSet UserClaims { get; set; } public DbSet UserRoles { get; set; } // ... your custom DbSets public DbSet RoleOperations { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove(); modelBuilder.Conventions.Remove(); // Configure Asp Net Identity Tables modelBuilder.Entity().ToTable("User"); modelBuilder.Entity().Property(u => u.PasswordHash).HasMaxLength(500); modelBuilder.Entity().Property(u => u.Stamp).HasMaxLength(500); modelBuilder.Entity().Property(u => u.PhoneNumber).HasMaxLength(50); modelBuilder.Entity().ToTable("Role"); modelBuilder.Entity().ToTable("UserRole"); modelBuilder.Entity().ToTable("UserLogin"); modelBuilder.Entity().ToTable("UserClaim"); modelBuilder.Entity().Property(u => u.ClaimType).HasMaxLength(150); modelBuilder.Entity().Property(u => u.ClaimValue).HasMaxLength(500); } } 

Opção 3: você terá um DbContext igual à opção 2. Vamos chamar de IdentityContext. E você terá outro DbContext chamado DXContext:

 public class DXContext : DbContext { public DXContext() : base("name=DXContext") // connection string in the application configuration file. { Database.SetInitializer(null); // Remove default initializer Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; } // Domain Model public DbSet 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(); // IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser modelBuilder.Entity().ToTable("User"); } public DbQuery Query() where T : class { return Set().AsNoTracking(); } } 

onde o usuário é:

 public class User { public int Id { get; set; } [Required, StringLength(100)] public string Name { get; set; } [Required, StringLength(128)] public string SomeOtherColumn { get; set; } } 

Com esta solução estou mapeando o usuário entity para a mesma tabela que a entidade ApplicationUser.

Então, usando Code First Migrations, você precisará gerar as migrações para o IdentityContext e THEN para o DXContext, seguindo este ótimo post de Shailendra Chauhan: Codificar Primeiras Migrações com Múltiplos Contextos de Dados

Você terá que modificar a migration gerada para o DXContext. Algo como isso, dependendo de quais propriedades são compartilhadas entre o ApplicationUser e o 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)); 

e, em seguida, executando as migrações em ordem (primeiro as migrações de identidade) do global.asax ou de qualquer outro local de seu aplicativo usando essa class personalizada:

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

Dessa forma, minhas entidades de corte cruzado de n camadas não acabam herdando das classs AspNetIdentity e, portanto, não preciso importar essa estrutura em todos os projetos em que as utilizo.

Desculpe pelo extenso post. Espero que possa oferecer alguma orientação sobre isso. Já usei as opções 2 e 3 em ambientes de produção.

ATUALIZAÇÃO: Expandir Opção 1

Nos dois últimos projetos, usei a primeira opção: ter uma class AspNetUser derivada do IdentityUser e uma class personalizada separada chamada AppUser. No meu caso, os DbContexts são IdentityContext e DomainContext respectivamente. E eu defini o Id do AppUser assim:

 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 é uma class base abstrata personalizada que eu uso no método SaveChanges do meu contexto DomainContext)

Eu primeiro criei o AspNetUser e depois o AppUser. A desvantagem dessa abordagem é que você tem certeza de que sua funcionalidade “CreateUser” é transacional (lembre-se de que haverá dois DbContexts chamando SaveChanges separadamente). Usar o TransactionScope não funcionou para mim por algum motivo, então acabei fazendo algo feio, mas isso funciona para mim:

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

(Por favor, se alguém vier com uma maneira melhor de fazer essa parte, eu aprecio comentar ou propor uma edição para esta resposta)

Os benefícios são que você não precisa modificar as migrações e pode usar qualquer hierarquia de inheritance maluca sobre o AppUser sem mexer no AspNetUser . E na verdade eu uso Migrações Automáticas para o meu IdentityContext (o contexto que deriva do IdentityDbContext):

 public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration { public IdentityMigrationConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = false; } protected override void Seed(IdentityContext context) { } } 

Essa abordagem também tem o benefício de evitar que suas entidades de corte cruzado herdem das classs AspNetIdentity.

No meu caso, eu tinha herdado do IdentityDbContext corretamente (com meus próprios tipos personalizados e chave definida), mas tinha inadvertidamente removido a chamada para o OnModelCreating da class base:

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

Em seguida, corrigi os índices ausentes das classs de identidade e gerava migrações e ativava as migrações de forma adequada.

Alterando o DbContext como abaixo;

  protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove(); modelBuilder.Conventions.Remove(); } 

Apenas adicionando no método OnModelCreating, chame para base.OnModelCreating (modelBuilder); e fica bem. Eu estou usando o EF6.

Agradecimentos especiais ao #The Senator

Para aqueles que usam o ASP.NET Identity 2.1 e mudaram a chave primária da string padrão para int ou Guid , se você ainda estiver obtendo

EntityType ‘xxxxUserLogin’ não possui chave definida. Defina a chave para este EntityType.

EntityType ‘xxxxUserRole’ não possui chave definida. Defina a chave para este EntityType.

você provavelmente esqueceu de especificar o novo tipo de chave no IdentityDbContext :

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

Se você acabou de ter

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

ou até mesmo

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

você receberá o erro “sem chave definida” quando estiver tentando adicionar migrações ou atualizar o database.

  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().ToTable("Users"); modelBuilder.Entity>().ToTable("Roles"); modelBuilder.Entity>().ToTable("UserTokens"); modelBuilder.Entity>().ToTable("UserClaims"); modelBuilder.Entity>().ToTable("UserLogins"); modelBuilder.Entity>().ToTable("RoleClaims"); modelBuilder.Entity>().ToTable("UserRoles"); } } 

Meu problema era semelhante – eu tinha uma nova tabela que estava criando para conectar aos usuários de identidade. Depois de ler as respostas acima, percebi que tinha a ver com IsdentityUser e os properites herdados. Eu já tinha configuração de identidade como seu próprio contexto, para evitar inerentemente amarrar os dois juntos, em vez de usar a tabela de usuário relacionada como uma propriedade EF real, configurei uma propriedade não mapeada com a consulta para obter as entidades relacionadas. (O DataManager está configurado para recuperar o contexto atual no qual o OtherEntity existe.)

  [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 GetOtherEntitiesByUserId(string userId) { return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity); } } public partial class ApplicationUser : IdentityUser { public async Task GenerateUserIdentityAsync(UserManager 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 OtherEntities { get { return UserOtherEntities.GetOtherEntitiesByUserId(this.Id); } } }