.NET Core 2.x Identity int 外键不能以 int 主键为目标

Jer*_*emi 7 c# invalidoperationexception entity-framework-core asp.net-core asp.net-core-identity

我有一个运行 .net core 2.x 的 Web 应用程序(最终刚刚从 .net core 1.x 升级),并且我已经将大部分内容从 .net core 1.x 移植到 2.x。然而,我的身份实现遇到了砖墙。它在 .net core 1.x 上运行良好,但现在它拒绝了。

该实现在 Entity Framework Core 上运行,并首先构建数据库(在预先存在的数据库中实现)。

我的问题是,当我现在尝试使用 .net core 2.x 登录时,我收到一条错误消息,指出:

InvalidOperationException:从 'AspNetUserRole.AspNetRole' 到具有外键属性 {'RoleId' : int} 的 'AspNetUserRole.AspNetRole' 的关系无法定位主键 {'Id' : int},因为它不兼容。为此关系配置一个主键或一组兼容的外键属性。

对我来说,这完全没有意义。int外键如何与int主键不兼容?

上下文和类的实际实现如下(这是一个非常简单的实现):

public partial class AspNetUser : IdentityUser<int>
{ }
public partial class AspNetRole : IdentityRole<int>
{ }
public partial class AspNetRoleClaim : IdentityRoleClaim<int>
{ }
public partial class AspNetUserClaim : IdentityUserClaim<int>
{ }
public partial class AspNetUserRole : IdentityUserRole<int>
{ }
public partial class AspNetUserToken : IdentityUserToken<int>
{ }
public partial class AspNetUserLogin : IdentityUserLogin<int>
{ }

public class IdentityDataContext : IdentityDbContext<AspNetUser, AspNetRole, int, AspNetUserClaim, AspNetUserRole, AspNetUserLogin, AspNetRoleClaim, AspNetUserToken>
{
    public IdentityDataContext(DbContextOptions<IdentityDataContext> options) : base(options)
    { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<AspNetUser>()
            .HasMany(e => e.AspNetUserClaims)
            .WithOne()
            .HasForeignKey(e => e.UserId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetUserClaim>()
           .HasOne(x => x.AspNetUser)
           .WithMany(x => x.AspNetUserClaims)
           .HasForeignKey(x => x.UserId);

        builder.Entity<AspNetUser>()
            .HasMany(e => e.AspNetUserLogins)
            .WithOne()
            .HasForeignKey(e => e.UserId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetUserLogin>()
           .HasOne(x => x.AspNetUser)
           .WithMany(x => x.AspNetUserLogins)
           .HasForeignKey(x => x.UserId);

        builder.Entity<AspNetUser>()
            .HasMany(e => e.AspNetUserRoles)
            .WithOne()
            .HasForeignKey(e => e.UserId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetUserRole>()
           .HasOne(x => x.AspNetUser)
           .WithMany(x => x.AspNetUserRoles)
           .HasForeignKey(x => x.UserId);

        builder.Entity<AspNetRole>()
            .HasMany(e => e.AspNetUserRoles)
            .WithOne()
            .HasForeignKey(e => e.RoleId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetUserRole>()
            .HasOne(x => x.AspNetRole)
            .WithMany(x => x.AspNetUserRoles)
            .HasForeignKey(x => x.RoleId);

        builder.Entity<AspNetUserRole>()
            .HasOne(x => x.AspNetUser)
            .WithMany(x => x.AspNetUserRoles)
            .HasForeignKey(x => x.UserId);

        builder.Entity<AspNetRole>()
            .HasMany(e => e.AspNetRoleClaims)
            .WithOne()
            .HasForeignKey(e => e.RoleId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetRoleClaim>()
           .HasOne(x => x.AspNetRole)
           .WithMany(x => x.AspNetRoleClaims)
           .HasForeignKey(x => x.RoleId);

        builder.Entity<AspNetUser>()
            .HasMany(e => e.AspNetUserTokens)
            .WithOne()
            .HasForeignKey(e => e.UserId)
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);

        builder.Entity<AspNetUserToken>()
           .HasOne(x => x.AspNetUser)
           .WithMany(x => x.AspNetUserTokens)
           .HasForeignKey(x => x.UserId);
    }
}
Run Code Online (Sandbox Code Playgroud)

它抱怨的两个类定义为:

[Table("AspNetUserRoles")]
public partial class AspNetUserRole
{
    [Key]
    public int Id { get; set; }
    [ForeignKey("AspNetUser")]
    public override int UserId { get; set; }
    [ForeignKey("AspNetRole")]
    public override int RoleId { get; set; }
    public string ConcurrencyStamp { get; set; }
    public int CreatedById { get; set; }
    public System.DateTime CreatedDate { get; set; }
    public Nullable<int> ChangedById { get; set; }
    public Nullable<System.DateTime> ChangedDate { get; set; }
    public bool IsDisabled { get; set; }

    [JsonIgnore]
    public virtual AspNetRole AspNetRole { get; set; }
    [JsonIgnore]
    public virtual AspNetUser AspNetUser { get; set; }
}

[Table("AspNetRoles")]
public partial class AspNetRole
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public AspNetRole()
    {
        this.AspNetRoleClaims = new HashSet<AspNetRoleClaim>();
        this.AspNetUserRoles = new HashSet<AspNetUserRole>();
    }

    [Key]
    public override int Id { get; set; }
    public override string Name { get; set; }
    public override string NormalizedName { get; set; }
    public override string ConcurrencyStamp { get; set; }
    public int CreatedById { get; set; }
    public System.DateTime CreatedDate { get; set; }
    public Nullable<int> ChangedById { get; set; }
    public Nullable<System.DateTime> ChangedDate { get; set; }
    public bool IsDisabled { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<AspNetRoleClaim> AspNetRoleClaims { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<AspNetUserRole> AspNetUserRoles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这实际上让我完全难住了。

编辑:当中断并尝试评估DbSet它引发错误的位置时,我得到一个指向ModelEvaluator. 老实说,这对我帮助很小。

在 Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNoShadowKeys(IModel 模型)\r\n 在 Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel 模型)\r\n 在 Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel 模型) \r\n 在 Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel 模型)\r\n 在 Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext 上下文,IConventionSetBuilder 约定SetBuilder,IModelValidator 验证器)\r\n 在 System.Lazy '1.ViaFactory(LazyThreadSafetyMode 模式)\r\n--- 从上一个抛出异常的位置开始的堆栈跟踪结束 ---\r\n 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
在 System.Lazy'1.CreateValue()\r\n 在 Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()\r\n 在 Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()\r\n 在 Microsoft.Extensions .DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)\r\n 在 Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructor\r\n Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructor\r\n Microsoft. .CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)\r\n 在 Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider,键入 serviceType)\r\n 在 Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)\r\n 在 Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()\r\n 在 Microsoft.EntityFrameworkCore.DbContext.get_Model ()\r\n 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityType()\r\n 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityQueryable()\r\n 在 Microsoft.EntityFrameworkCore.Internal.InternalDbSet '1.System.Collections.Generic.IEnumerable.GetEnumerator()\r\n 在 System.Collections.Generic.LargeArrayBuilder'1.AddRange(IEnumerable'1 items)\r\n 在 System.Collections.Generic.EnumerableHelpers.ToArray [T](IEnumerable'1 源)\r\n 在 System.Linq.Enumerable.ToArray[TSource](IEnumerable'1 个源)\r\n 在 System.Linq.SystemCore_EnumerableDebugView'1.get_Items()

编辑 2:根据建议,尝试OnModelCreating在我定义不同外键的地方添加一个(参见上面我的 IdentityDataContext 的定义) - 没有运气。

编辑 3:OnModelCreating答案是:如果您愿意,我刚刚错过了“反向”定义。例如,定义

builder.Entity<AspNetUser>()
    .HasMany(e => e.AspNetUserRoles)
    .WithOne()
    .HasForeignKey(e => e.UserId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Restrict);
builder.Entity<AspNetRole>()
    .HasMany(e => e.AspNetUserRoles)
    .WithOne()
    .HasForeignKey(e => e.RoleId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Restrict);
Run Code Online (Sandbox Code Playgroud)

还不够 - 您还必须添加相反的内容:

builder.Entity<AspNetUserRole>()
    .HasOne(x => x.AspNetUser)
    .WithMany(x => x.AspNetUserRoles)
    .HasForeignKey(x => x.UserId);

builder.Entity<AspNetUserRole>()
    .HasOne(x => x.AspNetRole)
    .WithMany(x => x.AspNetUserRoles)
    .HasForeignKey(x => x.RoleId);
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 5

异常消息不是很清楚,但通常表示模型配置不正确。

这里有几个因素需要考虑。

首先,在 2.0 版中,导航属性已从身份模型中删除,并且基本IndentityDbCOntext实现显式配置了在任一侧都没有导航属性的关系。

最后一点非常重要。EF Core 使用约定、数据注释和显式配置(通过 fluent API),约定优先级最低,显式配置优先级最高。这意味着数据注释可以覆盖约定,但不能覆盖显式配置。显式配置可以覆盖约定和数据注释,以及之前的显式配置(最后一个获胜)。换句话说,覆盖显式配置的唯一方法是在基本配置之后使用 fluent API。

由于您的模型添加了一些导航属性,因此您必须重新配置关系以反映这一点。关系配置的常见错误是使用Has/With方法而不指定导航属性名称/表达式,而实际上模型确实具有导航属性。从逻辑上讲,您认为跳过可选参数意味着使用 default,但在这里它实际上意味着没有导航属性。这反过来又会导致以下意外行为。

导航属性仍由 EF 发现。由于它们不是已配置关系的一部分,因此 EF 将它们视为单独关系的一部分,并按照惯例将它们映射到默认的影子 FK 属性/列名称。这绝对不是你想要的。

无需两次配置关系。实际上最好配置一次,但使用正确的With/ Hascall 参数来表示那端导航属性的存在/不存在。

话虽如此,您必须覆盖OnModelCreating,调用基本实现,然后添加以下内容以反映在您的身份模型派生实体中引入的导航属性:

builder.Entity<AspNetUserRole>()
    .HasOne(x => x.AspNetUser)
    .WithMany(x => x.AspNetUserRoles)
    .HasForeignKey(x => x.UserId);

builder.Entity<AspNetUserRole>()
    .HasOne(x => x.AspNetRole)
    .WithMany(x => x.AspNetUserRoles)
    .HasForeignKey(x => x.RoleId);
Run Code Online (Sandbox Code Playgroud)

与其他导航属性(如AspNetRole.AspNetRoleClaims集合等)类似。有关详细信息,请参阅解释不同关系配置的关系EF Core 文档主题

此外,由于默认情况下IdentityUserRole(再次明确)配置为使用复合 PK ( { UserId, RoleId }) 并且您的派生AspNetUserRole实体定义了自己的 PK ( Id),您还应该明确指定:

builder.Entity<AspNetUserRole>()
    .HasKey(e => e.Id);
Run Code Online (Sandbox Code Playgroud)