.NET Core 2.x Identity int 外键不能以 int 主键为目标
.NET Core 2.x Identity int foreign key cannot target int primary key
我有一个 Web 应用程序 运行 .net core 2.x(刚刚从 .net core 1.x 最终升级)并且我已经从 .net core 1.x 到 2.x。然而,我的 Identity 实现遇到了障碍。它在 .net 核心 1.x 上运行良好,但现在它拒绝了。
实现是 运行 在 Entity Framework 核心上,首先构建数据库(在预先存在的数据库中实现)。
我的问题是,当我现在尝试使用 .net core 2.x 登录时,我收到一条错误消息,指出:
InvalidOperationException: The relationship from
'AspNetUserRole.AspNetRole' to 'AspNetRole.AspNetUserRoles' with
foreign key properties {'RoleId' : int} cannot target the primary key
{'Id' : int} because it is not compatible. Configure a principal key
or a set of compatible foreign key properties for this relationship.
这对我来说完全没有意义。 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
的堆栈跟踪。老实说,这对我帮助不大。
at
Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNoShadowKeys(IModel
model)\r\n at
Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel
model)\r\n at
Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel
model)\r\n at
Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel
model)\r\n at
Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext
context, IConventionSetBuilder conventionSetBuilder, IModelValidator
validator)\r\n at System.Lazy'1.ViaFactory(LazyThreadSafetyMode
mode)\r\n--- End of stack trace from previous location where exception
was thrown ---\r\n at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Lazy'1.CreateValue()\r\n at
Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()\r\n
at
Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()\r\n
at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite
scopedCallSite, ServiceProviderEngineScope scope)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite
constructorCallSite, ServiceProviderEngineScope scope)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite
scopedCallSite, ServiceProviderEngineScope scope)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider
provider, Type serviceType)\r\n at
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider
provider)\r\n at
Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()\r\n
at Microsoft.EntityFrameworkCore.DbContext.get_Model()\r\n at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityType()\r\n
at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityQueryable()\r\n
at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.System.Collections.Generic.IEnumerable.GetEnumerator()\r\n
at
System.Collections.Generic.LargeArrayBuilder'1.AddRange(IEnumerable'1
items)\r\n at
System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable'1
source)\r\n at System.Linq.Enumerable.ToArray[TSource](IEnumerable'1
source)\r\n at
System.Linq.SystemCore_EnumerableDebugView'1.get_Items()
编辑 2:
根据建议,尝试在我定义不同外键的地方添加一个 OnModelCreating
(请参阅上面我的 IdentityDataContext 的定义)- 运气不好。
编辑 3:
OnModelCreating
就是答案:如果您愿意的话,我刚刚错过了 "reversed" 定义。例如定义
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);
我认为 id
是 Long
不是 int
所以关系应该在 long
数据类型之间,而不是 int
试试看!!
过去,我使用 int
数据类型做关系,EF 迁移不接受它。
异常消息不是很清楚,但通常表示模型配置不正确。
这里有几个因素需要考虑。
首先,在 2.0 版中,导航属性已从身份模型中删除,基本 IndentityDbCOntext
实现 显式 配置关系 没有任一侧的导航 属性.
最后一点很重要。 EF Core 使用约定、数据注释和显式配置(通过 fluent API),其中约定优先级最低,显式配置优先级最高。这意味着数据注释可以覆盖约定,但不能覆盖显式配置。显式配置可以覆盖约定和数据注释,以及之前的显式配置(最后胜出)。换句话说,覆盖显式配置的唯一方法是在基本配置之后使用 fluent API。
由于您的模型添加了一些导航属性,您必须重新配置关系以反映这一点。关系配置的常见错误是使用 Has
/ With
方法而不指定导航 属性 名称/表达式,而实际上是模型 do有导航 属性。逻辑上你认为跳过可选参数意味着使用默认值,但在这里它实际上意味着无导航属性。这反过来会导致以下意外行为。
导航属性仍由 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);
对我来说,这是通过在任何其他流畅代码之前调用基本 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。
我们可以使用 HasPrincipalKey 来定义我们想要link我们的外键
的字段
public class Menu
{
[Key]
public int Id { get; set; }
public string MenuCode { get; set; }
public Route Route { get; set; }
}
public class Route
{
[Key]
public int Id { get; set; }
public string MenuCode { get; set; }
public string Name { get; set; }
public Menu Menu { get; set; }
}
修复
modelBuilder.Entity<Menu>()
.HasOne(x => x.Route)
.WithOne(x => x.Menu)
.HasPrincipalKey<Menu>(x => x.MenuCode)
.HasForeignKey<Route>(x => x.MenuCode);
请查看此博客了解更多详情。
https://gavilan.blog/2019/04/14/entity-framework-core-foreign-key-linked-with-a-non-primary-key/
我有一个 Web 应用程序 运行 .net core 2.x(刚刚从 .net core 1.x 最终升级)并且我已经从 .net core 1.x 到 2.x。然而,我的 Identity 实现遇到了障碍。它在 .net 核心 1.x 上运行良好,但现在它拒绝了。
实现是 运行 在 Entity Framework 核心上,首先构建数据库(在预先存在的数据库中实现)。
我的问题是,当我现在尝试使用 .net core 2.x 登录时,我收到一条错误消息,指出:
InvalidOperationException: The relationship from 'AspNetUserRole.AspNetRole' to 'AspNetRole.AspNetUserRoles' with foreign key properties {'RoleId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
这对我来说完全没有意义。 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
的堆栈跟踪。老实说,这对我帮助不大。
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNoShadowKeys(IModel model)\r\n at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model)\r\n at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)\r\n at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)\r\n at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)\r\n at System.Lazy'1.ViaFactory(LazyThreadSafetyMode mode)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at System.Lazy'1.CreateValue()\r\n at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()\r\n at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)\r\n at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)\r\n at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)\r\n at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()\r\n at Microsoft.EntityFrameworkCore.DbContext.get_Model()\r\n at Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityType()\r\n at Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.get_EntityQueryable()\r\n at Microsoft.EntityFrameworkCore.Internal.InternalDbSet'1.System.Collections.Generic.IEnumerable.GetEnumerator()\r\n at System.Collections.Generic.LargeArrayBuilder'1.AddRange(IEnumerable'1 items)\r\n at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable'1 source)\r\n at System.Linq.Enumerable.ToArray[TSource](IEnumerable'1 source)\r\n at System.Linq.SystemCore_EnumerableDebugView'1.get_Items()
编辑 2:
根据建议,尝试在我定义不同外键的地方添加一个 OnModelCreating
(请参阅上面我的 IdentityDataContext 的定义)- 运气不好。
编辑 3:
OnModelCreating
就是答案:如果您愿意的话,我刚刚错过了 "reversed" 定义。例如定义
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);
我认为 id
是 Long
不是 int
所以关系应该在 long
数据类型之间,而不是 int
试试看!!
过去,我使用 int
数据类型做关系,EF 迁移不接受它。
异常消息不是很清楚,但通常表示模型配置不正确。
这里有几个因素需要考虑。
首先,在 2.0 版中,导航属性已从身份模型中删除,基本 IndentityDbCOntext
实现 显式 配置关系 没有任一侧的导航 属性.
最后一点很重要。 EF Core 使用约定、数据注释和显式配置(通过 fluent API),其中约定优先级最低,显式配置优先级最高。这意味着数据注释可以覆盖约定,但不能覆盖显式配置。显式配置可以覆盖约定和数据注释,以及之前的显式配置(最后胜出)。换句话说,覆盖显式配置的唯一方法是在基本配置之后使用 fluent API。
由于您的模型添加了一些导航属性,您必须重新配置关系以反映这一点。关系配置的常见错误是使用 Has
/ With
方法而不指定导航 属性 名称/表达式,而实际上是模型 do有导航 属性。逻辑上你认为跳过可选参数意味着使用默认值,但在这里它实际上意味着无导航属性。这反过来会导致以下意外行为。
导航属性仍由 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);
对我来说,这是通过在任何其他流畅代码之前调用基本 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。
我们可以使用 HasPrincipalKey 来定义我们想要link我们的外键
的字段public class Menu
{
[Key]
public int Id { get; set; }
public string MenuCode { get; set; }
public Route Route { get; set; }
}
public class Route
{
[Key]
public int Id { get; set; }
public string MenuCode { get; set; }
public string Name { get; set; }
public Menu Menu { get; set; }
}
修复
modelBuilder.Entity<Menu>()
.HasOne(x => x.Route)
.WithOne(x => x.Menu)
.HasPrincipalKey<Menu>(x => x.MenuCode)
.HasForeignKey<Route>(x => x.MenuCode);
请查看此博客了解更多详情。
https://gavilan.blog/2019/04/14/entity-framework-core-foreign-key-linked-with-a-non-primary-key/