I am trying to store two completely different sets of users in one DB to be used in two different applications. Both users classes should have separate permissions, claims, logins etc.
The goal of IdentityFramework here is to provide basic authorization and authenfication mechanisms.
Packages used:
EntityFramework v6.1.3
Microsoft.AspNet.Identity.Core v2.2.1
Microsoft.AspNet.Identity.EntityFramework v2.2.1
MySql.Data v6.8.7
MySql.Data.Entity v6.8.7
AFAIK there is no option to do so using IdentityDbContext
, so I going to do it by myself.
Looking at the sources of IdentityDbContext
, the two major things it does are mapping and validation. It also performs check if the Identity V1 Schema is used, but it is not my case.
So I try to refactor my code to get rid of IdentityDbContext
usage. My Identity models are:
public class ReproUser : IdentityUser<int, ReproLogin, ReproUserRole, ReproClaim> { }
public class ReproClaim : IdentityUserClaim<int> { }
public class ReproUserRole : IdentityUserRole<int> { }
public class ReproLogin : IdentityUserLogin<int> { }
public class ReproRole : IdentityRole<int, ReproUserRole> { }
and mappings are
internal class ReproUserRoleMap : EntityTypeConfiguration<ReproUserRole>
{
public ReproUserRoleMap()
{
HasKey( r => new { r.UserId, r.RoleId } );
ToTable( typeof( ReproUserRole ).Name + "sBridge" );
Property( t => t.UserId ).HasColumnName( "UserId" );
Property( t => t.RoleId ).HasColumnName( "RoleId" );
}
}
internal class ReproLoginMap : EntityTypeConfiguration<ReproLogin>
{
public ReproLoginMap()
{
HasKey( l => l.UserId );
ToTable( typeof( ReproLogin ).Name + 's' );
Property( t => t.UserId ).HasColumnName( "UserId" );
Property( t => t.LoginProvider ).HasColumnName( "LoginProvider" );
Property( t => t.ProviderKey ).HasColumnName( "ProviderKey" );
}
}
internal class ReproClaimMap : EntityTypeConfiguration<ReproClaim>
{
public ReproClaimMap()
{
ToTable( typeof( ReproClaim ).Name + 's' );
Property( t => t.Id ).HasColumnName( "Id" );
Property( t => t.UserId ).HasColumnName( "UserId" );
Property( t => t.ClaimType ).HasColumnName( "ClaimType" );
Property( t => t.ClaimValue ).HasColumnName( "ClaimValue" );
}
}
internal class ReproRoleMap : EntityTypeConfiguration<ReproRole>
{
public ReproRoleMap()
{
ToTable( typeof( ReproRole ).Name + 's' );
Property( t => t.Id )
.HasColumnName( "Id" );
Property( r => r.Name )
.HasColumnName( "Name" )
.IsRequired()
.HasMaxLength( 64 )
.HasColumnAnnotation( "Index", new IndexAnnotation( new IndexAttribute( "RoleNameIndex" ) { IsUnique = true } ) );
HasMany( r => r.Users )
.WithRequired()
.HasForeignKey( ur => ur.RoleId );
}
}
internal class ReproUserMapping : EntityTypeConfiguration<ReproUser>
{
public ReproUserMapping()
{
HasKey( u => u.Id );
ToTable( "ReproUser" );
HasMany( u => u.Roles )
.WithRequired()
.HasForeignKey( ur => ur.UserId );
HasMany( u => u.Claims )
.WithRequired()
.HasForeignKey( uc => uc.UserId );
HasMany( u => u.Logins )
.WithRequired()
.HasForeignKey( l => l.UserId );
Property( u => u.UserName )
.IsRequired()
.HasMaxLength( 128 )
.HasColumnAnnotation( "Index", new IndexAnnotation( new IndexAttribute( "UserNameIndex" ) { IsUnique = true } ) );
Property( u => u.Email )
.HasMaxLength( 128 );
Property( u => u.Id )
.HasColumnName( "Id" );
}
}
So I mimic IdentityDbContext
mapping except for details (like table namings).
If I try to make migration at that point (assume that it's the first migration after the initial one), I get the following error:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
ReproUser_Logins_Target: : Multiplicity is not valid in Role 'ReproUser_Logins_Target' in relationship 'ReproUser_Logins'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
at System.Data.Entity.Core.Metadata.Edm.EdmModel.Validate()
at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
at System.Data.Entity.Internal.LazyInternalContext.get_ModelBeingInitialized()
at System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(DbContext context, XmlWriter writer)
at System.Data.Entity.Utilities.DbContextExtensions.<>c__DisplayClass1.<GetModel>b__0(XmlWriter w)
at System.Data.Entity.Utilities.DbContextExtensions.GetModel(Action`1 writeXml)
at System.Data.Entity.Utilities.DbContextExtensions.GetModel(DbContext context)
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration, DbContext usersContext, DatabaseExistenceState existenceState, Boolean calledByCreateDatabase)
at System.Data.Entity.Migrations.DbMigrator..ctor(DbMigrationsConfiguration configuration)
at System.Data.Entity.Migrations.Design.MigrationScaffolder..ctor(DbMigrationsConfiguration migrationsConfiguration)
at System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.Run()
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Scaffold(String migrationName, String language, String rootNamespace, Boolean ignoreChanges)
at System.Data.Entity.Migrations.AddMigrationCommand.Execute(String name, Boolean force, Boolean ignoreChanges)
at System.Data.Entity.Migrations.AddMigrationCommand.<>c__DisplayClass2.<.ctor>b__0()
at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
One or more validation errors were detected during model generation:
ReproUser_Logins_Target: : Multiplicity is not valid in Role 'ReproUser_Logins_Target' in relationship 'ReproUser_Logins'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
If I try to remove Logins to User mapping, the things are going to be much more weird. Migration is generated, but Logins table gets two columns for the one UserId
entity field:
CreateTable(
"dbo.ReproLogins",
c => new
{
UserId = c.Int(nullable: false, identity: true),
LoginProvider = c.String(unicode: false),
ProviderKey = c.String(unicode: false),
ReproUser_Id = c.Int(),
})
.PrimaryKey(t => t.UserId)
.ForeignKey("dbo.ReproUser", t => t.ReproUser_Id)
.Index(t => t.ReproUser_Id);
That ReproUser_Id
seems very unnatural for me. Even if I am not going to use LoginProviders at all, I am really confused about it's existance.
So the question is:
How to map Users 0..1 - 0..* Logins
without getting errors and rudimentary columns in DB?
How to resolve " Multiplicity is not valid in Role" error? solution isn't working for me. In addition, it will produce some problems at the stage of adding another user class because there should be navigation property in base login class or mapping should be specified explicitely for both of them instead of doing it in common generic method.
Having read error message more accurate the error reason becomes clear. The key to solve a problem with multiplicity is in "Because the Dependent Role refers to the key properties...". So the reason of the error is that I am trying to map one user
to many logins
while logins
have UserId
as both primary and foreign keys. But the primary key should be unique, so it can't be foreign key for single User in the relationship.
So the solution is to revert original logins
table PK from Identity Framework, e.g. set it to UserId+LoginProvider+ProviderKey
.