多重性在角色 1 中无效..* 或 EF 奇怪的迁移代码或如何在一个数据库中存储两组完全不同的用户
Multiplicity is not valid in Role 1..* OR EF weird migration code OR how to store two separate sets of completely different users in one DB
我试图在一个数据库中存储两组完全不同的用户,以供两个不同的应用程序使用。两个用户 classes 应该有单独的权限、声明、登录等。
此处 IdentityFramework 的目标是提供基本的授权和认证机制。
使用的包:
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
据我所知,无法使用 IdentityDbContext
这样做,所以我打算自己做。
查看 IdentityDbContext
的来源,它所做的两件主要事情是映射和验证。它还会检查是否使用了 Identity V1 Schema,但我的情况并非如此。
所以我尝试重构我的代码以摆脱 IdentityDbContext
用法。我的身份模型是:
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> { }
映射是
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" );
}
}
所以我模仿 IdentityDbContext
映射,除了细节(比如 table 命名)。
如果我此时尝试进行迁移(假设这是第一次迁移之后的第一次迁移),我会收到以下错误:
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'.
如果我尝试删除登录到用户的映射,事情会变得更加奇怪。已生成迁移,但登录 table 为一个 UserId
实体字段获取两列:
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);
那个 ReproUser_Id
对我来说似乎很不自然。即使我根本不打算使用 LoginProviders,我也对它的存在感到困惑。
所以问题是:
如何映射 Users 0..1 - 0..* Logins
而不会在数据库中出现错误和基本列?
解决方案对我不起作用。此外,它会在添加另一个用户class 阶段产生一些问题,因为在基本登录class 中应该有导航属性 或者应该为它们都明确指定映射而不是用通用的通用方法来做。
更准确地阅读错误消息,错误原因变得清晰。解决多重性问题的关键在于"Because the Dependent Role refers to the key properties..."。所以错误的原因是我试图将一个 user
映射到多个 logins
而 logins
将 UserId
作为主键和外键。但是主键应该是唯一的,所以它不能是关系中单个用户的外键。
因此解决方案是从 Identity Framework 恢复原始 logins
table PK,例如将其设置为 UserId+LoginProvider+ProviderKey
.
我试图在一个数据库中存储两组完全不同的用户,以供两个不同的应用程序使用。两个用户 classes 应该有单独的权限、声明、登录等。
此处 IdentityFramework 的目标是提供基本的授权和认证机制。
使用的包:
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
据我所知,无法使用 IdentityDbContext
这样做,所以我打算自己做。
查看 IdentityDbContext
的来源,它所做的两件主要事情是映射和验证。它还会检查是否使用了 Identity V1 Schema,但我的情况并非如此。
所以我尝试重构我的代码以摆脱 IdentityDbContext
用法。我的身份模型是:
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> { }
映射是
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" );
}
}
所以我模仿 IdentityDbContext
映射,除了细节(比如 table 命名)。
如果我此时尝试进行迁移(假设这是第一次迁移之后的第一次迁移),我会收到以下错误:
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'.
如果我尝试删除登录到用户的映射,事情会变得更加奇怪。已生成迁移,但登录 table 为一个 UserId
实体字段获取两列:
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);
那个 ReproUser_Id
对我来说似乎很不自然。即使我根本不打算使用 LoginProviders,我也对它的存在感到困惑。
所以问题是:
如何映射 Users 0..1 - 0..* Logins
而不会在数据库中出现错误和基本列?
更准确地阅读错误消息,错误原因变得清晰。解决多重性问题的关键在于"Because the Dependent Role refers to the key properties..."。所以错误的原因是我试图将一个 user
映射到多个 logins
而 logins
将 UserId
作为主键和外键。但是主键应该是唯一的,所以它不能是关系中单个用户的外键。
因此解决方案是从 Identity Framework 恢复原始 logins
table PK,例如将其设置为 UserId+LoginProvider+ProviderKey
.