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

.NET Core 6.0中使用Select创建IQueryable为何缓慢?

问题描述

我们有一个数据层(DataTier),其中包含代表各类表/列的模型,这些模型关联对应的DbContext(命名为OurDbContext)。

Foo模型定义:

public class Foo
{
    public string Field1 { get; set; }
    public string Field2 { get; set; }
}

由于需要给Foo添加一个数据库表中不存在的字段,定义了继承自Foo的Bar类:

public class Bar : Foo
{
    public string Field3 { get; set; }
}

尝试创建支持延迟执行的IQueryable<Bar>:

IQueryable<Bar> queryable = OurDbContext.Foo.FromSqlRaw(someSQl)
    .AsNoTracking()
    .Select(x => new Bar()
    {
        Field1 = x.Field1,
        Field2 = x.Field2,
        Field3 = null // 暂设为null,后续填充
    });

现象:Intellisense显示Select返回的是IQueryable,但实际创建该IQueryable时速度极慢(耗时10-15秒);而移除Select直接返回IQueryable<Foo>则瞬时完成:

IQueryable<Foo> queryable = OurDbContext.Foo.FromSqlRaw(someSQl)
    .AsNoTracking();

请问忽略了什么?

原因与解决方案

核心问题是EF Core在处理Select中构造**继承自实体类的非实体类型(Bar)**时,无法正确生成延迟执行的查询计划,提前触发了查询执行,这就是你看到耗时的根源。

具体原因

EF Core的查询提供者会尝试把Linq表达式翻译成SQL,但Bar不是DbContext中注册的实体类型(你只注册了Foo)。当你在Select里构造Bar实例时,EF Core没法把这个构造逻辑映射到SQL,于是会立即执行前面的FromSqlRaw查询,把所有Foo数据加载到内存,再在内存中逐个转换成Bar对象。而直接返回IQueryable时,EF Core只是生成查询计划,并没有实际访问数据库,所以瞬时完成。

解决办法

有两种实用的处理方式:

  1. 改用独立DTO类而非继承实体类
    不要让Bar继承自Foo,定义一个独立的DTO类包含所需字段:

    public class BarDto
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }
    

    然后Select构造这个DTO:

    IQueryable<BarDto> queryable = OurDbContext.Foo.FromSqlRaw(someSQl)
        .AsNoTracking()
        .Select(x => new BarDto()
        {
            Field1 = x.Field1,
            Field2 = x.Field2,
            Field3 = null
        });
    

    这种情况下EF Core能正确识别这是投影操作,保持延迟执行,不会提前加载数据。

  2. 先转成IEnumerable再构造Bar(适合小数据量场景)
    如果一定要用继承自Foo的Bar,可以先把IQueryable转成IEnumerable(此时会触发数据库查询),再在内存中转换。但注意这会把所有数据加载到内存,只适合数据量不大的情况:

    IEnumerable<Bar> queryable = OurDbContext.Foo.FromSqlRaw(someSQl)
        .AsNoTracking()
        .AsEnumerable() // 这里触发数据库查询
        .Select(x => new Bar()
        {
            Field1 = x.Field1,
            Field2 = x.Field2,
            Field3 = null
        });
    

    这种方式本质上是明确了查询执行时机,耗时和你之前看到的一致。

额外提示

别让非实体类继承EF Core的实体类,实体类是和数据库表绑定设计的,继承会让EF Core误以为是实体继承映射(比如TPH/TPT),从而产生不必要的查询解析开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:02:47