The Entity Type 'Microsoft.Aspnet.Identity.Entityframework.
Identityuserlogin<String>' Requires a Key to Be Defined

The entity type 'IdentityUserLoginstring' requires a primary key to be defined

keys of Identity tables are mapped in OnModelCreating method of IdentityDbContext and if this method is not called, you will end up getting the error that you got.

All you need to do is call.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}

The original answer (just copied for other's reference just in case) here

IdentityUserLoginstring' requires a primary key to be defined error while adding migration

To reduce the link to a nutshell, try this:

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

...

See Above link for more.

The entity type 'Microsoft.AspNet.Identity.EntityFramework.Identity
UserLoginstring' requires a key to be defined

Basically the keys of Identity tables are mapped in OnModelCreating method of IdentityDbContext and if this method is not called, you will end up getting the error that you got. This method is not called if you derive from IdentityDbContext and provide your own definition of OnModelCreating as you did in your code. With this setup you have to explicitly call the OnModelCreating method of IdentityDbContext using base.OnModelCreating statement.
This answer also discusses the option I posted here

Aspnetuserlogins' requires a primary key to be defined (interconnected databases)

The issue is because I was attempting to load from two interconnected databases without letting EF Core know that the data one is dependent on the user one. The problem can be fixed by changing the code to the following:

public partial class DataContext : UserContext {..}
public partial class UserContext : DbContext
{
public UserContext()
{
}

public UserContext(DbContextOptions options)
: base(options)
{
}
}

The entity type 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.
IdentityUserLoginstring' requires a key to be defined

In my OnModelCreating Method. I needed to place base.OnModelCreating(modelBuilder); as my first line of code.

EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType

The problem is that your ApplicationUser inherits from IdentityUser, which is defined like this:

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

and their primary keys are mapped in the method OnModelCreating of the class 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");

and as your DXContext doesn't derive from it, those keys don't get defined.

If you dig into the sources of Microsoft.AspNet.Identity.EntityFramework, you will understand everything.

I came across this situation some time ago, and I found three possible solutions (maybe there are more):

  1. Use separate DbContexts against two different databases or the same database but different tables.
  2. Merge your DXContext with ApplicationDbContext and use one database.
  3. Use separate DbContexts against the same table and manage their migrations accordingly.

Option 1:
See update the bottom.

Option 2:
You will end up with a DbContext like this one:

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

Option 3:
You will have one DbContext equal to the option 2. Let's name it IdentityContext. And you will have another DbContext called 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();
}
}

where User is:

public class User
{
public int Id { get; set; }

[Required, StringLength(100)]
public string Name { get; set; }

[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}

With this solution, I'm mapping the entity User to the same table as the entity ApplicationUser.

Then, using Code First Migrations you'll need to generate the migrations for the IdentityContext and THEN for the DXContext, following this great post from Shailendra Chauhan: Code First Migrations with Multiple Data Contexts

You'll have to modify the migration generated for DXContext. Something like this depending on which properties are shared between ApplicationUser and 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));

and then running the migrations in order (first the Identity migrations) from the global.asax or any other place of your application using this custom class:

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

This way, my n-tier cross-cutting entities don't end up inheriting from AspNetIdentity classes, and therefore I don't have to import this framework in every project where I use them.

Sorry for the extensive post. I hope it could offer some guidance on this. I have already used options 2 and 3 in production environments.

UPDATE: Expand Option 1

For the last two projects I have used the 1st option: having an AspNetUser class that derives from IdentityUser, and a separate custom class called AppUser. In my case, the DbContexts are IdentityContext and DomainContext respectively. And I defined the Id of the AppUser like this:

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 is the custom abstract base class that I use in the overridden SaveChanges method of my DomainContext context)

I first create the AspNetUser and then the AppUser. The drawback with this approach is that you have ensured that your "CreateUser" functionality is transactional (remember that there will be two DbContexts calling SaveChanges separately). Using TransactionScope didn't work for me for some reason, so I ended up doing something ugly but that works for 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;
}

(Please, if somebody comes with a better way of doing this part I appreciate commenting or proposing an edit to this answer)

The benefits are that you don't have to modify the migrations and you can use any crazy inheritance hierarchy over the AppUser without messing with the AspNetUser. And actually, I use Automatic Migrations for my IdentityContext (the context that derives from IdentityDbContext):

public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}

protected override void Seed(IdentityContext context)
{
}
}

This approach also has the benefit of avoiding to have your n-tier cross-cutting entities inheriting from AspNetIdentity classes.



Related Topics



Leave a reply



Submit