【发布时间】:2018-12-21 23:56:46
【问题描述】:
我有一个运行 .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' 到 'AspNetRole.AspNetUserRoles' 与 外键属性 {'RoleId' : int} 不能定位主键 {'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);
}
}
它抱怨的两个类被定义为:
[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; }
}
这真的把我完全难住了。
编辑:
当打破并尝试评估引发错误的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 范围)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope 范围)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite、ServiceProviderEngineScope 范围)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider 提供者,类型 serviceType)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider 提供者)\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 项目)\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);
还不够 - 你还必须添加反向:
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);
【问题讨论】:
-
可以发一下你
IdentityDataContext的流畅配置(OnModelCreating)吗? -
我根本没有使用 Fluent。
IdentityDataContext只是上面代码中的内容。部分类(AspNetRole、AspNetUserRole等)是从 edmx(数据库优先)生成的。 -
问题是导航属性已从身份类中删除,这反映在基本
IdentityDbContextfluent 配置中,它优先于数据注释/约定。由于您的模型具有导航属性,因此您必须在调用基本实现后覆盖它。参见例如stackoverflow.com/questions/45863522/…。 -
这是一个很好的起点!然而,一些
WithOne缺少反向导航属性,例如WithOne(e => e.AspNetUser),WithOne(e => e.AspNetRole)。还有AspNetUserRolePK -builder.Entity<AspNetUserRole>().HasKey(e => e.Id)。基本上所有与基本身份上下文配置不同的东西。 -
是的,我注意到并刚刚更新了这个问题。请张贴您的 cmets 作为答案,以便我接受并奖励赏金! :)
标签: c# asp.net-core entity-framework-core invalidoperationexception asp.net-core-identity