EntityType ‘IdentityUserLogin’ non ha una chiave definita. Definire la chiave per questo EntityType

Sto lavorando con Entity Framework Code First e MVC 5. Quando ho creato la mia applicazione con l’ autenticazione account utente individuale, mi è stato fornito un controller account e, insieme a esso, tutte le classi e il codice necessari per far funzionare l’autenticazione degli account utente di Indiv .

Tra i codici già in vigore c’era questo:

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

Ma poi sono andato avanti e ho creato il mio contesto usando prima il codice, quindi ora ho anche questo:

 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 ho il seguente metodo di seed per aggiungere alcuni dati con cui lavorare durante lo sviluppo:

 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, "[email protected]"); 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://sofit.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://sofit.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); } } } } 

La mia soluzione si integra perfettamente, ma quando provo ad accedere a un controller che richiede l’accesso al database ottengo il seguente errore:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType ‘IdentityUserLogin’ non ha alcuna chiave definita. Definire la chiave per questo EntityType.

DX.DOMAIN.Context.IdentityUserRole:: EntityType ‘IdentityUserRole’ non ha alcuna chiave definita. Definire la chiave per questo EntityType.

Che cosa sto facendo di sbagliato? È perché ho due contesti?

AGGIORNARE

Dopo aver letto la risposta di Augusto, sono andato con l’ opzione 3 . Ecco come appare la mia class DXContext ora:

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

Ho anche aggiunto una User.cs e una class Role.cs , hanno questo aspetto:

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

Non ero sicuro se avrei avuto bisogno di una password per l’utente, dal momento che l’ApplicationUser predefinito ha questo e un mucchio di altri campi!

Ad ogni modo, la modifica sopra riportata va bene, ma di nuovo ottengo questo errore quando viene eseguita l’applicazione:

Nome colonna non valido UserId

UserId è una proprietà intera sul mio Artist.cs

Il problema è che il tuo ApplicationUser eredita da IdentityUser , che è definito in questo modo:

 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 le loro chiavi primarie sono mappate nel metodo OnModelCreating della 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 dato che DXContext non ne deriva, quelle chiavi non vengono definite.

Se si analizzano i sorgenti di Microsoft.AspNet.Identity.EntityFramework , capirai tutto.

Mi sono imbattuto in questa situazione qualche tempo fa e ho trovato tre possibili soluzioni (forse ce ne sono altre):

  1. Utilizzare DbContexts separato su due database diversi o sullo stesso database ma su tabelle diverse.
  2. Unisci DXContext con ApplicationDbContext e utilizza un unico database.
  3. Utilizza DbContexts separati rispetto alla stessa tabella e gestisci le loro migrazioni di conseguenza.

Opzione 1: vedi aggiornare la parte inferiore.

Opzione 2: ti ritroverai con un DbContext come questo:

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

Opzione 3: avrai un DbContext uguale all’opzione 2. Chiamiamolo IdentityContext. E avrai un altro DbContext chiamato 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(); } } 

dove l’utente è:

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

Con questa soluzione sto mappando l’ quadro User alla stessa tabella dell’entity framework ApplicationUser.

Quindi, utilizzando Code First Migrations dovrai generare le migrazioni per IdentityContext e THEN per DXContext, seguendo questo ottimo post di Shailendra Chauhan: Code First Migrations with Multiple Data Contexts

Dovrai modificare la migrazione generata per DXContext. Qualcosa di simile a seconda delle proprietà condivise tra ApplicationUser e 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 quindi eseguendo le migrazioni in ordine (prima le migrazioni Identity) da global.asax o da qualsiasi altro luogo dell’applicazione utilizzando questa class personalizzata:

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

In questo modo, le mie quadro crossover n-tier non finiscono per ereditare dalle classi AspNetIdentity, e quindi non devo importare questo framework in ogni progetto in cui li utilizzo.

Ci scusiamo per il post esteso. Spero che possa offrire qualche indicazione su questo. Ho già utilizzato le opzioni 2 e 3 negli ambienti di produzione.

AGGIORNAMENTO: espandere l’opzione 1

Per gli ultimi due progetti ho utilizzato la prima opzione: avere una class AspNetUser che deriva da IdentityUser e una class personalizzata separata chiamata AppUser. Nel mio caso, i DbContexts sono rispettivamente IdentityContext e DomainContext. E ho definito l’Id di AppUser in questo modo:

 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 è una class base astratta personalizzata che uso nel metodo SaveChanges sovrascritto del mio contesto DomainContext)

Prima creo AspNetUser e poi AppUser. Lo svantaggio di questo approccio è che hai assicurato che la tua funzionalità “CreateUser” sia transazionale (ricorda che ci saranno due DbContex che chiamano SaveChanges separatamente). L’uso di TransactionScope non ha funzionato per me per qualche motivo, quindi ho finito per fare qualcosa di brutto, ma questo funziona per 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; } 

(Per favore, se qualcuno ha un modo migliore di fare questa parte, apprezzo il commento o propongo una modifica a questa risposta)

I vantaggi sono che non è necessario modificare le migrazioni ed è ansible utilizzare qualsiasi gerarchia di ereditarietà pazzesca su AppUser senza problemi con AspNetUser . E in realtà io uso Migrazioni automatiche per il mio IdentityContext (il contesto che deriva da IdentityDbContext):

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

Questo approccio ha anche il vantaggio di evitare che le quadro cross-cutting di livello superiore ereditino dalle classi AspNetIdentity.

Nel mio caso ho ereditato correttamente da IdentityDbContext (con i miei tipi personalizzati e la chiave definita) ma ho rimosso inavvertitamente la chiamata alla OnModelCreating della class base:

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

Che poi ha riparato gli indici mancanti dalle classi di id quadro e ho quindi potuto generare migrazioni e abilitare le migrazioni in modo appropriato.

Cambiando il DbContext come sotto;

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

Basta aggiungere nel metodo OnModelCreating chiamare a base.OnModelCreating (modelBuilder); e sta bene. Sto usando EF6.

Ringraziamenti speciali a #Il senatore

Per chi usa ASP.NET Identity 2.1 e ha modificato la chiave primaria dalla string predefinita a int o Guid , se si sta ancora ottenendo

EntityType ‘xxxxUserLogin’ non ha una chiave definita. Definire la chiave per questo EntityType.

EntityType ‘xxxxUserRole’ non ha una chiave definita. Definire la chiave per questo EntityType.

probabilmente ti sei dimenticato di specificare il nuovo tipo di chiave su IdentityDbContext :

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

Se hai appena

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

o anche

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

si otterrà l’errore ‘nessuna chiave definita’ quando si tenta di aggiungere migrazioni o aggiornare il 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"); } } 

Il mio problema era simile: avevo un nuovo tavolo che stavo creando e che collegavo agli utenti di identity framework. Dopo aver letto le risposte di cui sopra, si rese conto che aveva a che fare con IsdentityUser e le proprietà ereditate. Ho già impostato Identity come proprio Contesto, quindi per evitare di bind insieme i due, anziché utilizzare la tabella utente correlata come una vera proprietà EF, ho impostato una proprietà non mappata con la query per ottenere le quadro correlate. (DataManager è impostato per recuperare il contesto corrente in cui OtherEntity esiste.)

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