EntityType ‘IdentityUserLogin’ n’a pas de clé définie. Définir la clé pour ce EntityType

Je travaille avec Entity Framework Code First et MVC 5. Lorsque j’ai créé mon application avec l’authentification par comptes utilisateur individuels, j’ai reçu un contrôleur de compte ainsi que toutes les classes et le code requirejs pour que l’authentification des comptes utilisateurs Indiv fonctionne. .

Parmi les codes déjà en place figurait celui-ci:

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

Mais alors je suis allé de l’avant et j’ai créé mon propre contexte en utilisant le code en premier, j’ai donc maintenant ce qui suit:

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

Enfin, j’ai la méthode de démarrage suivante pour append des données avec lesquelles je peux travailler tout en développant:

 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(); ssortingng userId = ""; userId = context.Users.FirstOrDefault().Id; var artists = new List { new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://soffr.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://soffr.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); } } } } 

Ma solution se construit bien, mais lorsque j’essaie d’accéder à un contrôleur qui nécessite un access à la firebase database, j’obtiens l’erreur suivante:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType ‘IdentityUserLogin’ n’a pas de clé définie. Définissez la clé pour ce EntityType.

DX.DOMAIN.Context.IdentityUserRole :: EntityType ‘IdentityUserRole’ n’a pas de clé définie. Définissez la clé pour ce EntityType.

Qu’est-ce que je fais mal? Est-ce parce que j’ai deux contextes?

METTRE À JOUR

Après avoir lu la réponse d’Augusto, je suis allé avec Option 3 . Voici à quoi ressemble ma classe DXContext maintenant:

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

J’ai également ajouté une User.cs et une classe Role.cs , elles ressemblent à ceci:

 public class User { public int Id { get; set; } public ssortingng FName { get; set; } public ssortingng LName { get; set; } } public class Role { public int Id { set; get; } public ssortingng Name { set; get; } } 

Je n’étais pas sûr d’avoir besoin d’une propriété de mot de passe sur l’utilisateur, puisque l’application par défaut possède cela et un tas d’autres champs!

Quoi qu’il en soit, la modification ci-dessus se construit bien, mais encore une fois, j’obtiens cette erreur lorsque l’application est exécutée:

Nom de colonne non valide

UserId est une propriété entière sur mon Artist.cs

Le problème est que votre ApplicationUser hérite de IdentityUser , qui est défini comme ceci:

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

et leurs clés primaires sont mappées dans la méthode OnModelCreating de la classe 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"); 

et comme votre DXContext n’en dérive pas, ces clés ne sont pas définies.

Si vous creusez dans les sources de Microsoft.AspNet.Identity.EntityFramework , vous comprendrez tout.

Je suis tombé sur cette situation il y a quelque temps et j’ai trouvé trois solutions possibles (peut-être qu’il y en a plus):

  1. Utilisez séparément DbContexts sur deux bases de données différentes ou la même firebase database, mais des tables différentes.
  2. Fusionnez votre DXContext avec ApplicationDbContext et utilisez une firebase database.
  3. Utilisez des DbContexts distincts sur la même table et gérez leurs migrations en conséquence.

Option 1: Voir mise à jour en bas.

Option 2: Vous allez vous retrouver avec un DbContext comme celui-ci:

 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: vous aurez un DbContext égal à l’option 2. Appelons-le IdentityContext. Et vous aurez un autre DbContext appelé DXContext:

 public class DXContext : DbContext { public DXContext() : base("name=DXContext") // connection ssortingng 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(); } } 

où l’utilisateur est:

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

Avec cette solution, je mappe l’entité User sur la même table que l’entité ApplicationUser.

Ensuite, en utilisant Code First Migrations, vous devrez générer les migrations pour IdentityContext et THEN pour DXContext, en suivant cet excellent post de Shailendra Chauhan: Premières migrations de code avec plusieurs contextes de données

Vous devrez modifier la migration générée pour DXContext. Quelque chose comme cela dépend des propriétés partagées entre ApplicationUser et User:

  //CreateTable( // "dbo.User", // c => new // { // Id = c.Int(nullable: false, identity: true), // Name = c.Ssortingng(nullable: false, maxLength: 100), // SomeOtherColumn = c.Ssortingng(nullable: false, maxLength: 128), // }) // .PrimaryKey(t => t.Id); AddColumn("dbo.User", "SomeOtherColumn", c => c.Ssortingng(nullable: false, maxLength: 128)); 

puis exécuter les migrations dans l’ordre (d’abord les migrations d’identité) à partir de global.asax ou de tout autre endroit de votre application à l’aide de cette classe personnalisée:

 public static class DXDatabaseMigrator { public static ssortingng ExecuteMigrations() { return ssortingng.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(), ExecuteDXMigrations()); } private static ssortingng ExecuteIdentityMigrations() { IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration(); return RunMigrations(configuration); } private static ssortingng ExecuteDXMigrations() { DXMigrationConfiguration configuration = new DXMigrationConfiguration(); return RunMigrations(configuration); } private static ssortingng 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" : ssortingng.Join(", ", pendingMigrations); } } 

De cette façon, mes entités transversales n-tiers ne finissent pas par hériter des classes AspNetIdentity et, par conséquent, je n’ai pas à importer cette structure dans tous les projets où je les utilise.

Désolé pour le poste étendu. J’espère qu’il pourrait vous donner des indications à ce sujet. J’ai déjà utilisé les options 2 et 3 dans les environnements de production.

MISE À JOUR: Développez l’option 1

Pour les deux derniers projets, j’ai utilisé la première option: avoir une classe AspNetUser dérivée de IdentityUser et une classe personnalisée distincte appelée AppUser. Dans mon cas, les DbContexts sont IdentityContext et DomainContext respectivement. Et j’ai défini l’ID de AppUser comme ceci:

 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 est une classe de base abstraite personnalisée que j’utilise dans la méthode SaveChanges remplacée de mon contexte DomainContext)

Je crée d’abord AspNetUser puis AppUser. L’inconvénient de cette approche est que vous devez vous assurer que votre fonctionnalité “CreateUser” est transactionnelle (rappelez-vous qu’il y aura deux DbContexts appelant SaveChanges séparément). L’utilisation de TransactionScope ne fonctionnait pas pour moi pour une raison quelconque, alors j’ai fini par faire quelque chose de moche mais cela fonctionne pour moi:

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

(S’il vous plaît, si quelqu’un vient avec une meilleure façon de faire cette partie, j’apprécie de commenter ou de proposer une modification à cette réponse)

Les avantages sont que vous n’avez pas à modifier les migrations et vous pouvez utiliser n’importe quelle hiérarchie d’inheritance folle sur l’AppUser sans interférer avec AspNetUser . Et en fait, j’utilise les migrations automatiques pour mon IdentityContext (le contexte qui dérive de IdentityDbContext):

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

Cette approche a également l’avantage d’éviter que vos entités transversales à n-niveaux héritent des classes AspNetIdentity.

Dans mon cas, j’avais hérité d’IdentityDbContext correctement (avec mes propres types et clés personnalisés), mais j’avais par inadvertance supprimé l’appel à OnModelCreating de la classe de base:

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

Qui a ensuite corrigé mes index manquants à partir des classes d’identité et j’ai alors pu générer des migrations et activer les migrations de manière appropriée.

En changeant le DbContext comme ci-dessous;

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

Il suffit d’append dans la méthode OnModelCreating l’appel à base.OnModelCreating (modelBuilder); et ça va bien. J’utilise EF6.

Merci spécial à #The Senator

Pour ceux qui utilisent ASP.NET Identity 2.1 et ont changé la clé primaire de la ssortingng par défaut en soit int ou Guid , si vous obtenez toujours

EntityType ‘xxxxUserLogin’ n’a pas de clé définie. Définissez la clé pour ce EntityType.

EntityType ‘xxxxUserRole’ n’a pas de clé définie. Définissez la clé pour ce EntityType.

vous avez probablement juste oublié de spécifier le nouveau type de clé sur IdentityDbContext :

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

Si vous avez juste

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

ou même

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

vous obtiendrez cette erreur «aucune clé définie» lorsque vous essayez d’append des migrations ou de mettre à jour la firebase database.

  protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys())) // relationship.DeleteBehavior = DeleteBehavior.Ressortingct; 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"); } } 

Mon problème était similaire – j’avais une nouvelle table que je créais et que je mettrais en relation avec les utilisateurs de l’identité. Après avoir lu les réponses ci-dessus, j’ai réalisé que cela avait à voir avec IsdentityUser et les propriétés héritées. J’avais déjà installé Identity comme son propre contexte, donc pour éviter de lier les deux ensemble, plutôt que d’utiliser la table utilisateur associée comme une vraie propriété EF, j’ai configuré une propriété non mappée avec la requête pour obtenir les entités associées. (DataManager est configuré pour récupérer le contexte actuel dans lequel OtherEntity existe.)

  [Table("UserOtherEntity")] public partial class UserOtherEntity { public Guid UserOtherEntityId { get; set; } [Required] [SsortingngLength(128)] public ssortingng UserId { get; set; } [Required] public Guid OtherEntityId { get; set; } public virtual OtherEntity OtherEntity { get; set; } } public partial class UserOtherEntity : DataManager { public static IEnumerable GetOtherEntitiesByUserId(ssortingng 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); } } }