EF Core的单表继承:在子元素中对基类型进行查询时失败,错误信息为“对实体类型 'Base' 的成员 'Parent' 的翻译失败”
我正在尝试在一个类似的模型下编写如下的查询。我认为我理解问题所在,但还没能以不在基表中新增外键列的方式来编辑模型。
另外,也想知道为什么当我把bases添加到parent时,会在 Base 表中新增 Base、D1 和 D2 这几列。这似乎是最不符合预期的结果。
public List<Base> Bases { get; set; }
错误:
未处理的异常。System.InvalidOperationException: The LINQ expression 'DbSet()
.Join(inner: DbSet(),
outerKeySelector: b => EF.Property(b, "BaseId"), innerKeySelector: b0 => EF.Property (b0, "ID"), resultSelector: (o, i) => new TransparentIdentifier ( Outer = o, Inner = i )) .Where(b => b.Inner.Parent.ID == 1)' could not be translated. 额外信息: Translation of member 'Parent' on entity type 'Base' failed. This commonly occurs when the specified member is unmapped. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpressionTResult
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorTResult
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQueryTResult
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCoreTResult
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass11_01.<ExecuteCore>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCoreTResult
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsyncTResult
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsyncTResult
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetAsyncEnumerator(CancellationToken cancellationToken) at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsyncTSource
at Program.$(String[] args) in C:\Users\Anthony\RiderProjects\efcore-tph\Program.cs:line 5
at Program.(String[] args)
查询:
await context.BaseChildren
.Where(child => child.Base.Parent.ID == 1)
.ToListAsync();
NuGet软件包
命令行:
dotnet nuget add Microsoft.EntityFrameworkCore.Design
dotnet nuget add Microsoft.EntityFrameworkCore
dotnet nuget add Npgsql.EntityFrameworkCore.PostgreSQL
包管理器
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore
Install-Package Npgsql.EntityFrameworkCore.PostgreSQL
Program.cs
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using var context = new MyContext();
await context.BaseChildren
.Where(child => child.Base.Parent.ID == 123)
.ToListAsync();
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<D1> D1s { get; set; }
public DbSet<D2> D2s { get; set; }
public DbSet<BaseChild> BaseChildren { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseNpgsql(@"Host=localhost;Username=postgres;Password=password;Database=mydatabase");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Base>()
.HasDiscriminator<string>("type")
.HasValue<Base>("base")
.HasValue<D1>("d1")
.HasValue<D2>("d2");
}
}
public class Parent
{
public int ID { get; set; }
public List<D1> D1s { get; set; }
public List<D2> D2s { get; set; }
}
public abstract class Base
{
public int ID { get; set; }
public string Url { get; set; }
public int ParentId { get; set; }
[ForeignKey(nameof(ParentId))]
public Parent Parent { get; set; }
public List<BaseChild> Children { get; set; }
}
public class BaseChild
{
public int ID { get; set; }
public string Property { get; set; }
public int BaseId { get; set; }
[ForeignKey(nameof(BaseId))]
public Base Base { get; set; }
}
public class D1 : Base
{
public string D1Property { get; set; }
}
public class D2 : Base
{
public string D2Property { get; set; }
}
编辑:我理解这些替代方案,但想知道为什么实体无法弄清楚BaseChild和 Base之间的映射。
如果我写一个如下的查询,它确实可以工作,因此我想了解为什么在相反的顺序下它不工作。我可以推测原因,但需要一个明确的答案,或许还有一些文档。至今我找到的资料没有哪一个像我提供的示例那样深入。
await context.Parents
.Include(p => p.D1s)
.ThenInclude(d1 => d1.Children)
.Include(p => p.D2s)
.ThenInclude(d2 => d2.Children)
.ToListAsync();
解决方案
我不认为这会起作用:
public class Parent
{
public int ID { get; set; }
public List<D1> D1s { get; set; }
public List<D2> D2s { get; set; }
}
这将需要两个外键,一个在D1上,一个在D2上,因为ParentID外键位于Base,EF将无法将其解析到适用于集合的某个D1或 D2。
从父实体的角度来考虑映射:
.HasMany(p => p.D1s)
.WithOne(d1 => d1.Parent) // Base.ParentId
.HasMany(p => p.D2s)
.WithOne(d2 => d2.Parent) // Base.ParentId Cannot have 2 mappings to same FK
EF不允许对同一个外键建立两种关系。相反,我们可以通过基类型集合来映射单一关系:
public class Parent
{
public int ID { get; set; }
public List<Base> Children { get; private set; } = [];
[NotMapped]
public IEnumerable<D1> D1s => Children.OfType<D1>();
[NotMapped]
public IEnumerable<D2> D2s => Children.OfType<D2>();
}
在这里,父对象与Base(s) 之间的关系被映射,且需要对Bases(子对象)进行急加载以访问D1s或 D2s。同样地,当通过D1或 D2来查询父对象时,必须通过 parent => parent.Children 进行。要让不同的集合引用可用,最好使用TPC继承映射。