EntityType ‘IdentityUserLogin’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType

Ich arbeite mit Entity Framework Code First und MVC 5. Als ich meine Anwendung mit Individual User Accounts Authentication erstellte, erhielt ich einen Account Controller und damit alle benötigten classn und Code, die benötigt werden, um die Authentifizierung der Indiv User Accounts zu ermöglichen .

Unter dem bereits vorhandenen Code war dies:

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

Aber dann ging ich voran und erstellte zuerst meinen eigenen Kontext mit Code, also habe ich jetzt auch folgendes:

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

Schließlich habe ich die folgende Seed-Methode, um einige Daten hinzuzufügen, mit denen ich während der Entwicklung arbeiten kann:

 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://sofde.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://sofde.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); } } } } 

Meine Lösung baut sich gut auf, aber wenn ich versuche, auf einen Controller zuzugreifen, der Zugriff auf die database benötigt, erhalte ich den folgenden Fehler:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType ‘IdentityUserLogin’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

DX.DOMAIN.Context.IdentityUserRole:: EntityType ‘IdentityUserRole’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

Was mache ich falsch? Liegt es daran, dass ich zwei Kontexte habe?

AKTUALISIEREN

Nachdem ich Augustos Antwort gelesen hatte, ging ich mit Option 3 . So sieht meine DXContext-class jetzt aus:

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

Ich habe auch eine User.cs und eine Role.cs class hinzugefügt, sie sehen so aus:

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

Ich war mir nicht sicher, ob ich eine Kennworteigenschaft für den Benutzer benötigen würde, da der Standardanwendungsbenutzer dies und eine Reihe anderer Felder hat!

Wie auch immer, die obige Änderung baut sich gut auf, aber wieder bekomme ich diesen Fehler, wenn die Anwendung ausgeführt wird:

Ungültiger Spaltenname UserId

UserId ist eine Integer-Eigenschaft für meine Artist.cs

Solutions Collecting From Web of "EntityType ‘IdentityUserLogin’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType"

Das Problem ist, dass Ihr ApplicationUser von IdentityUser erbt , der wie folgt definiert ist:

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

und ihre Primärschlüssel werden in der Methode OnModelCreating der class IdentityDbContext zugeordnet :

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

und da Ihr DXContext nicht davon abgeleitet ist, werden diese Schlüssel nicht definiert.

Wenn Sie sich in die Quellen von Microsoft.AspNet.Identity.EntityFramework , werden Sie alles verstehen.

Ich bin vor einiger Zeit auf diese Situation gestoßen und habe drei mögliche Lösungen gefunden (vielleicht gibt es mehr):

  1. Verwenden Sie separate DbContexts für zwei verschiedene databaseen oder die gleiche database, aber verschiedene Tabellen.
  2. Verbinden Sie Ihren DXContext mit ApplicationDbContext und verwenden Sie eine database.
  3. Verwenden Sie separate DbContexte für die gleiche Tabelle und verwalten Sie ihre Migrationen entsprechend.

Option 1: Siehe update the bottom.

Option 2: Sie werden mit einem DbContext wie diesem enden:

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

Option 3: Sie haben einen DbContext, der der Option 2 entspricht. Wir nennen ihn IdentityContext. Und Sie werden einen anderen DbContext namens DXContext haben:

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

Wo ist der Benutzer?

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

Mit dieser Lösung ordne ich die Entität Benutzer der gleichen Tabelle wie die Entität ApplicationUser zu.

Dann müssen Sie mit Code First Migrations die Migrationen für den IdentityContext und THEN für den DXContext generieren, gefolgt von diesem großartigen Beitrag von Shailendra Chauhan: Code First Migrations mit mehreren Datenkontexten

Sie müssen die für DXContext generierte Migration ändern. So etwas hängt davon ab, welche Eigenschaften zwischen ApplicationUser und User geteilt werden:

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

und dann die Migrationen in der Reihenfolge (zuerst die Identity-Migrationen) von global.asax oder einem anderen Ort Ihrer Anwendung ausführen, der diese benutzerdefinierte class verwendet:

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

Auf diese Weise erben meine n-Tier-Cross-Cutting-Entities nicht von AspNetIdentity-classn und daher muss ich dieses Framework nicht in jedes Projekt importieren, in dem ich sie verwende.

Entschuldigung für den umfangreichen Beitrag. Ich hoffe, es könnte eine Anleitung dazu geben. Ich habe bereits Optionen 2 und 3 in Produktionsumgebungen verwendet.

UPDATE: Erweitern Sie Option 1

Für die letzten beiden Projekte habe ich die erste Option verwendet: eine AspNetUser-class, die von IdentityUser abgeleitet ist, und eine separate benutzerdefinierte class namens AppUser. In meinem Fall sind die DbContexts IdentityContext bzw. DomainContext. Und ich habe die ID des AppUser so definiert:

 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 ist eine benutzerdefinierte abstrakte Basisklasse, die ich in der überschriebenen SaveChanges-Methode meines DomainContext-Kontexts verwende)

Ich erstelle zuerst den AspNetUser und dann den AppUser. Der Nachteil bei diesem Ansatz ist, dass Sie sicherstellen, dass Ihre “CreateUser” -functionalität transaktional ist (denken Sie daran, dass es zwei DbContexte geben wird, die SaveChanges separat aufrufen). Die Verwendung von TransactionScope funktionierte für mich aus irgendeinem Grund nicht, also habe ich etwas Hässliches getan, aber das funktioniert für mich:

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

(Bitte, wenn jemand mit einem besseren Weg kommt, um diesen Teil zu machen, schätze ich es, diese Antwort zu kommentieren oder zu bearbeiten)

Die Vorteile sind, dass Sie die Migrationen nicht ändern müssen und Sie können jede verrückte inheritanceshierarchie über den AppUser verwenden, ohne den AspNetUser zu stören . Und tatsächlich verwende ich automatische Migrationen für meinen IdentityContext (der Kontext, der von IdentityDbContext abgeleitet wird):

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

Dieser Ansatz hat auch den Vorteil, dass Ihre n-tierübergreifenden Entitäten nicht von AspNetIdentity-classn geerbt werden.

In meinem Fall hatte ich den IdentityDbContext korrekt geerbt (mit meinen eigenen benutzerdefinierten Typen und Schlüssel definiert), aber versehentlich den Aufruf der OnModelCreating der Basisklasse entfernt:

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

Dadurch wurden meine fehlenden Indizes aus den Identitätsklassen korrigiert und ich konnte dann Migrationen generieren und Migrationen entsprechend aktivieren.

Indem Sie den DbContext wie unten ändern;

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

Fügen Sie onModelCreating-Methode einfach in base.OnModelCreating (modelBuilder); und es wird gut. Ich benutze EF6.

Besonderer Dank an #Der Senator

Für diejenigen, die ASP.NET Identity 2.1 verwenden und den Primärschlüssel von der Standardzeichenfolge entweder in int oder in Guid geändert haben, wenn Sie noch erhalten

EntityType ‘xxxxUserLogin’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

EntityType ‘xxxxUserRole’ hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

Sie haben wahrscheinlich vergessen, den neuen Schlüsseltyp für IdentityDbContext anzugeben:

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

Wenn du es nur getan hast

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

oder auch

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

Sie erhalten den Fehler “Kein Schlüssel definiert”, wenn Sie versuchen, Migrationen hinzuzufügen oder die database zu aktualisieren.

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

Mein Problem war ähnlich – ich hatte eine neue Tabelle, die ich erstellen wollte und die ich an die Identität der Benutzer binden sollte. Nachdem ich die obigen Antworten gelesen hatte, erkannte ich, dass es mit IsdentityUser und den ererbten Eigenschaften zu tun hatte. Ich hatte Identity bereits als eigenen Context eingerichtet, um die beiden nicht zusammen zu binden, anstatt die verwandte Benutzertabelle als echte EF-Eigenschaft zu verwenden, richtete ich eine nicht zugeordnete Eigenschaft mit der Abfrage ein, um die zugehörigen Entitäten zu erhalten. (DataManager ist eingerichtet, um den aktuellen Kontext abzurufen, in dem OtherEntity existiert.)

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