多重性在角色 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 映射到多个 loginsloginsUserId 作为主键和外键。但是主键应该是唯一的,所以它不能是关系中单个用户的外键。

因此解决方案是从 Identity Framework 恢复原始 logins table PK,例如将其设置为 UserId+LoginProvider+ProviderKey.