EF Core的单表继承:在子元素中对基类型进行查询时失败,错误信息为“对实体类型 'Base' 的成员 'Parent' 的翻译失败”

编程语言 2026-07-10

我正在尝试在一个类似的模型下编写如下的查询。我认为我理解问题所在,但还没能以不在基表中新增外键列的方式来编辑模型。

另外,也想知道为什么当我把bases添加到parent时,会在 Base 表中新增 BaseD1D2 这几列。这似乎是最不符合预期的结果。

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继承映射。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章