You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core 7.0.15使用TPC映射与AutoInclude时引发无效转换异常

问题描述

使用.NET 6.0 + PostgreSQL,通过TPC映射策略让所有实体继承DbBase抽象类,相关代码及遇到的问题如下:

DbContext配置

public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

// devices tracking
public DbSet<Device> Devices { get; set; }
public DbSet<DeviceLending> DevicesLendings { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Default values from db functions
    modelBuilder
        .Entity<DbBase>()
        .UseTpcMappingStrategy()
        .Property(e => e.Created)
        .HasDefaultValueSql("now()");

    // Auto Includes
    modelBuilder
        .Entity<Device>()
        .Navigation(e => e.Lendings)
        .AutoInclude();

    modelBuilder
        .Entity<DeviceLending>()
        .Navigation(e => e.Device)
        .AutoInclude();
}

DbBase类定义

public abstract class DbBase
{
    [Key] public int Id { get; set; }
    public Instant Created { get; set; }
    public Instant Updated { get; set; }
    
    [Timestamp]
    public uint Version { get; set; }
}

实体类定义

public class Device : DbBase
{
    public ICollection<DeviceLending> Lendings { get; } = new List<DeviceLending>();
    public string Comment { get; set; } = string.Empty;
}

public class DeviceLending : DbBase
{
    public string Comment { get; set; } = String.Empty;
    public int DeviceId { get; set; }
    public Device Device { get; set; } = null!;
}

加载包含Lendings属性的Device数据时抛出异常:

System.InvalidCastException: Unable to cast object of type 'devices.Entities.Device' to type 'devices.Entities.DeviceLending'

注释AutoInclude配置后可正常运行,但需要自动加载关联的Lendings数据,添加Lendings属性的setter也无效果。完整异常信息:

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit '5xqWOiz4ASoSDGW8EUUVSgQvQ-XQTuwqp0axa7fVeFk'.
System.InvalidCastException: Unable to cast object of type 'devices.Entities.Device' to type 'devices.Entities.DeviceLending'.
at lambda_method90(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.g__ProcessCurrentElementRow|24_0[TIncludingEntity,TIncludedEntity](<>c__DisplayClass24_02& ) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.PopulateIncludeCollection[TIncludingEntity,TIncludedEntity](Int32 collectionId, QueryContext queryContext, DbDataReader dbDataReader, SingleQueryResultCoordinator resultCoordinator, Func3 parentIdentifier, Func3 outerIdentifier, Func3 selfIdentifier, IReadOnlyList1 parentIdentifierValueComparers, IReadOnlyList1 outerIdentifierValueComparers, IReadOnlyList1 selfIdentifierValueComparers, Func5 innerShaper, INavigationBase inverseNavigation, Action2 fixup, Boolean trackingQuery) at lambda_method92(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator ) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)
at devices.Services.DbService.GetDevicesAsync(CancellationToken ct) in [redacted]/Services/DbService.cs:line 68
at devices.Shared.DeviceGrid.LoadDevices() in [redacted]/Shared/DeviceGrid.razor:line 40
at devices.Shared.DeviceGrid.OnInitializedAsync() in [redacted]/Shared/DeviceGrid.razor:line 45
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)


原因分析

这个异常确实和TPC映射策略直接相关:

  1. TPC(Table-Per-Concrete-Type)策略会为每个具体实体创建独立数据表,每个表包含实体所有属性(包括继承自基类的字段)。但你直接给抽象基类DbBase配置TPC,导致EF Core在处理关联时,错误地将基类的Id字段当作关联匹配键,混淆了Device和DeviceLending表的结果行,触发类型转换错误。
  2. 同时配置双向AutoInclude(Device.Lendings和DeviceLending.Device互相自动包含),会生成循环关联的JOIN查询,进一步加剧结果塑形逻辑的混乱。

解决方案

1. 修正TPC配置方式

不要直接给抽象基类配置TPC,而是为每个具体实体单独配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 为每个具体实体配置TPC策略
    modelBuilder.Entity<Device>().UseTpcMappingStrategy();
    modelBuilder.Entity<DeviceLending>().UseTpcMappingStrategy();

    // 分别为每个实体设置基类属性的默认值
    modelBuilder.Entity<Device>()
        .Property(e => e.Created)
        .HasDefaultValueSql("now()");
    modelBuilder.Entity<DeviceLending>()
        .Property(e => e.Created)
        .HasDefaultValueSql("now()");
}

2. 显式配置关联关系

明确指定外键,避免EF Core混淆基类Id:

// 在OnModelCreating中添加
modelBuilder.Entity<DeviceLending>()
    .HasOne(dl => dl.Device)
    .WithMany(d => d.Lendings)
    .HasForeignKey(dl => dl.DeviceId)
    .OnDelete(DeleteBehavior.Cascade);

3. 移除双向AutoInclude

保留单向自动包含即可,避免循环查询:

// 只保留Device到Lendings的自动包含
modelBuilder.Entity<Device>()
    .Navigation(e => e.Lendings)
    .AutoInclude();

// 注释掉反向的AutoInclude
// modelBuilder.Entity<DeviceLending>()
//     .Navigation(e => e.Device)
//     .AutoInclude();

4. 验证查询SQL

通过EF Core日志查看生成的SQL语句,确认JOIN逻辑是通过DeviceId关联两张表,而非基类的Id字段。


内容的提问来源于stack exchange,提问作者atcheckmate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:05:55