我有一个运行.net core 2.x的Web应用程序(最后才从.net core 1.x升级),并且我已经将大部分内容从.net core 1.x移植到了2.x。但是,我的Identity实施遇到了障碍。在.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 ConventionSetBuilder,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位于 Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite ConstructorCallSite,ServiceProviderEngineScope范围)\ r \ n位于 Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite,ServiceProviderEngineScope范围)\ r \ n位于 Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider 提供者,在以下位置键入serviceType)\ r \ n 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);
答案 0 :(得分:3)
该异常消息不太清楚,但通常表示模型配置不正确。
这里有几个要考虑的因素。
首先,在2.0版中,导航属性已从身份模型中删除,并且基本的IndentityDbCOntext
实现明确配置了在没有导航属性的情况下的关系 。
最后一点很重要。 EF Core使用约定,数据注释和显式配置(通过fluent API),约定的优先级最低,显式配置的优先级最高。这意味着数据注释可以覆盖约定,但不能显式配置。显式配置可以覆盖约定和数据注释,以及先前的显式配置(最后一个获胜者)。换句话说,覆盖显式配置的唯一方法是在基本配置之后使用fluent API。
由于您的模型添加了一些导航属性,因此您必须重新配置关系以反映这一点。关系配置的常见错误是,实际上模型 do 具有导航属性时,使用Has
/ With
方法而不指定导航属性名称/表达式。从逻辑上讲,您认为跳过可选参数意味着使用默认值,但实际上这意味着没有导航属性。继而导致以下意外行为。
EF仍发现导航属性。由于它们不是已配置关系的一部分,因此EF认为它们是 separate 关系的一部分,并且通常以默认的影子FK属性/列名映射它们。绝对不是您想要的。
无需两次配置关系。实际上,最好配置一次,但是要使用正确的With
/ Has
调用参数来表示那一端是否存在导航属性。
话虽如此,您必须重写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);
以及其他类似AspNetRole.AspNetRoleClaims
集合等导航属性的属性。有关更多信息,请参见Relationships EF Core文档主题,解释不同的关系配置
此外,由于默认情况下(再次明确地)将IdentityUserRole
配置为使用复合PK({ UserId, RoleId }
),并且派生的AspNetUserRole
实体定义了自己的PK(Id
),您还应该明确指定:
builder.Entity<AspNetUserRole>()
.HasKey(e => e.Id);
答案 1 :(得分:0)
我认为id
不是Long
因此关系应该在int
数据类型之间,而不是long
之间
试试吧!
过去,我使用int
数据类型建立关系,而EF迁移不接受它。
答案 2 :(得分:0)
对我来说,这是通过在其他任何流利代码之前调用基本OnModelCreating方法来解决的:
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserDefinition>()
.ToTable("UserDefinition");
// .HasOne(x => x.Subscription)
// .WithMany(x => x.UserDefinitions)
// .HasForeignKey(x => x.SubscriptionId);
modelBuilder.Entity<Subscription>()
.ToTable("Subscription");
// .HasMany(x => x.UserDefinitions)
// .WithOne()
// .HasForeignKey(x => x.SubscriptionId);
我在这里EF Core 2.0 Identity - Adding navigation properties找到了答案。