the identityuserrole has for entitytype define ef-code-first asp.net-mvc-5 seeding

ef code first - identityuserrole - EntityType ''IdentityUserLogin'' no tiene una clave definida. Definir la clave para este EntityType



entitytype ''identityuserrole'' has no key defined. define the key for this entitytype. (6)

Al cambiar el DbContext de la siguiente manera;

protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); }

Simplemente agregue la llamada al método OnModelCreating a base.OnModelCreating (modelBuilder); Y se pone bien. Estoy usando EF6.

Un agradecimiento especial a #The Senator

Estoy trabajando con Entity Framework Code First y MVC 5. Cuando creé mi aplicación con autenticación de cuentas de usuario individual, me dieron un controlador de cuenta y, junto con él, todas las clases y el código necesarios para que funcione la autenticación de cuentas de usuario Indiv .

Entre el código ya establecido estaba este:

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

Pero luego seguí adelante y creé mi propio contexto usando primero el código, así que ahora también tengo lo siguiente:

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

Finalmente, tengo el siguiente método de inicialización para agregar algunos datos con los que trabajar mientras desarrollo:

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

Mi solución funciona bien, pero cuando intento acceder a un controlador que requiere acceso a la base de datos, aparece el siguiente error:

DX.DOMAIN.Context.IdentityUserLogin:: EntityType ''IdentityUserLogin'' no tiene una clave definida. Defina la clave para este EntityType.

DX.DOMAIN.Context.IdentityUserRole:: EntityType ''IdentityUserRole'' no tiene una clave definida. Defina la clave para este EntityType.

¿Qué estoy haciendo mal? ¿Es porque tengo dos contextos?

ACTUALIZAR

Después de leer la respuesta de Augusto, fui con la Opción 3 . Así es como se ve mi clase DXContext ahora:

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

También agregué una User.cs y una clase Role.cs , se ven así:

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

No estaba seguro de si necesitaría una propiedad de contraseña en el usuario, ya que el ApplicationUser predeterminado tiene eso y muchos otros campos.

De todos modos, el cambio anterior se construye bien, pero nuevamente obtengo este error cuando se ejecuta la aplicación:

Nombre de columna no válido ID de usuario

UserId es una propiedad entera en mi Artist.cs


El problema es que su ApplicationUser hereda de IdentityUser , que se define así:

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

y sus claves principales se asignan en el método OnModelCreating de la clase IdentityDbContext :

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

y como su DXContext no se deriva de él, esas claves no se definen.

Si profundiza en las sources de Microsoft.AspNet.Identity.EntityFramework , lo comprenderá todo.

Encontré esta situación hace algún tiempo, y encontré tres posibles soluciones (tal vez hay más):

  1. Use DbContexts separados contra dos bases de datos diferentes o la misma base de datos pero tablas diferentes.
  2. Combine su DXContext con ApplicationDbContext y use una base de datos.
  3. Use DbContexts separados en la misma tabla y administre sus migraciones en consecuencia.

Opción 1: ver actualización al final.

Opción 2: terminarás con un DbContext como este:

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

Opción 3: tendrá un DbContext igual a la opción 2. Llamémoslo IdentityContext. Y tendrá otro DbContext llamado DXContext:

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

donde está el usuario:

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 esta solución, estoy asignando la entidad Usuario a la misma tabla que la entidad ApplicationUser.

Luego, usando Code First Migrations necesitará generar las migraciones para IdentityContext y THEN para DXContext, siguiendo esta gran publicación de Shailendra Chauhan: Code First Migrations con múltiples contextos de datos

Tendrá que modificar la migración generada para DXContext. Algo como esto dependiendo de qué propiedades se comparten entre ApplicationUser y 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));

y luego ejecutando las migraciones en orden (primero las migraciones de Identidad) desde global.asax o cualquier otro lugar de su aplicación usando esta clase 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<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); } }

De esta manera, mis entidades transversales de n niveles no terminan heredando de las clases AspNetIdentity, y por lo tanto no tengo que importar este marco en cada proyecto donde las uso.

Perdón por la extensa publicación. Espero que pueda ofrecer alguna orientación sobre esto. Ya he usado las opciones 2 y 3 en entornos de producción.

ACTUALIZACIÓN: expanda la opción 1

Para los últimos dos proyectos, he usado la primera opción: tener una clase AspNetUser que se deriva de IdentityUser y una clase personalizada separada llamada AppUser. En mi caso, los DbContexts son IdentityContext y DomainContext respectivamente. Y definí el ID del usuario de la aplicación de esta manera:

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 es una clase base abstracta personalizada que uso en el método SaveChanges anulado de mi contexto DomainContext)

Primero creo el AspNetUser y luego el AppUser. El inconveniente de este enfoque es que debe asegurarse de que su funcionalidad "CreateUser" sea transaccional (recuerde que habrá dos DbContexts llamando a SaveChanges por separado). Usar TransactionScope no funcionó para mí por alguna razón, así que terminé haciendo algo feo, pero eso funciona para mí:

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, si alguien viene con una mejor manera de hacer esta parte, agradezco comentar o proponer una edición para esta respuesta)

Los beneficios son que no tiene que modificar las migraciones y puede usar cualquier jerarquía de herencia loca sobre AppUser sin meterse con AspNetUser . Y en realidad uso Migraciones automáticas para mi IdentityContext (el contexto que se deriva de IdentityDbContext):

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

Este enfoque también tiene el beneficio de evitar que sus entidades transversales de n niveles hereden de las clases AspNetIdentity.


En mi caso, he heredado correctamente del IdentityDbContext (con mis propios tipos y claves personalizados definidos) pero sin querer eliminé la llamada a OnModelCreating de la clase base:

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

Lo que luego solucionó mis índices faltantes de las clases de identidad y luego pude generar migraciones y habilitar las migraciones adecuadamente.


Mi problema era similar: tenía una nueva tabla que estaba creando que estaba vinculada a los usuarios de identidad. Después de leer las respuestas anteriores, me di cuenta de que tenía que ver con IsdentityUser y las propiedades heredadas. Ya tenía la identidad configurada como su propio contexto, por lo que para evitar unir inherentemente los dos, en lugar de usar la tabla de usuario relacionada como una verdadera propiedad EF, configuré una propiedad no asignada con la consulta para obtener las entidades relacionadas. (DataManager está configurado para recuperar el contexto actual en el que existe 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); } } }


Para aquellos que usan ASP.NET Identity 2.1 y han cambiado la clave primaria de la string predeterminada a int o Guid , si todavía están obteniendo

EntityType ''xxxxUserLogin'' no tiene una clave definida. Defina la clave para este EntityType.

EntityType ''xxxxUserRole'' no tiene una clave definida. Defina la clave para este EntityType.

probablemente olvidó especificar el nuevo tipo de clave en IdentityDbContext :

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

Si solo tienes

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

o incluso

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

obtendrá el error "sin clave definida" cuando intente agregar migraciones o actualizar la base de datos.


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