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

EF Core如何通过DbContext查询导航属性指定字段

EF Core 嵌套导航属性指定字段查询性能对比与方案选型

问题背景

正在探索最高效的EF Core查询写法,用于选取导航属性中的嵌套指定字段,确保API响应仅包含所需的相关信息。目前已尝试两种实现方案:

  • 使用Include()搭配ThenInclude()的贪婪加载方案
  • 社区推荐的Select()投影方案

测试发现两种方案生成的数据库查询逻辑高度相似,从数据库检索的列完全一致;唯一可观测的差异是:Include()方案会将非必要列的数据加载到实体对象的对应属性中,而Select()方案不会执行这部分多余加载。
当前项目采用分层架构模式,不适合使用匿名类型作为返回值,且实体中未赋值的null属性不会造成业务问题——最终仅会将查询到的有效字段映射到DTO中,但仍然关注查询本身的执行效率问题,想确认:

  1. 是否存在更优的从导航属性查询单列字段的方案?
  2. 如果不存在更优方案,上述两种写法中哪一种性能更好?

现有实现与生成SQL对比

1. Include贪婪加载实现

public async Task<IEnumerable<Orden>> GetDetailed()
{
    return await _context.Ordenes
                         .Include(o => o.Detalles)
                         .ThenInclude(od => od.ProductosAlmacenados)
                         .Include(o => o.Detalles)
                         .ThenInclude(od => od.Tela)
                         .Include(o => o.Detalles)
                         .ThenInclude(od => od.Color)
                         .ToListAsync();
}

生成的SQL语句

SELECT `o`.`ID`, `o`.`Estatus`, `o`.`Fecha_Registro`, `o`.`Fecha_Requerida`, `t0`.`Color_ID`, `t0`.`Orden_ID`, `t0`.`Tela_ID`, `t0`.`Cantidad`, `t0`.`ID`, `t0`.`ID0`, `t0`.`Almacen_ID`, `t0`.`OrdenDetalles_Color_ID`, `t0`.`OrdenDetalles_Orden_ID`, `t0`.`OrdenDetalles_Tela_ID`, `t0`.`Cantidad0`, `t0`.`Nombre`, `t0`.`Nombre0`
  FROM `orden` AS `o`
  LEFT JOIN (
      SELECT `o0`.`Color_ID`, `o0`.`Orden_ID`, `o0`.`Tela_ID`, `o0`.`Cantidad`, `t`.`ID`, `c`.`ID` AS `ID0`, `a`.`Almacen_ID`, `a`.`OrdenDetalles_Color_ID`, `a`.`OrdenDetalles_Orden_ID`, `a`.`OrdenDetalles_Tela_ID`, `a`.`Cantidad` AS `Cantidad0`, `t`.`Nombre`, `c`.`Nombre` AS `Nombre0`
      FROM `ordendetalles` AS `o0`
      INNER JOIN `tela` AS `t` ON `o0`.`Tela_ID` = `t`.`ID`
      INNER JOIN `color` AS `c` ON `o0`.`Color_ID` = `c`.`ID`
      LEFT JOIN `almacen_productos` AS `a` ON ((`o0`.`Color_ID` = `a`.`OrdenDetalles_Color_ID`) AND (`o0`.`Orden_ID` = `a`.`OrdenDetalles_Orden_ID`)) AND (`o0`.`Tela_ID` = `a`.`OrdenDetalles_Tela_ID`)
  ) AS `t0` ON `o`.`ID` = `t0`.`Orden_ID`
  ORDER BY `o`.`ID`, `t0`.`Color_ID`, `t0`.`Orden_ID`, `t0`.`Tela_ID`, `t0`.`ID`, `t0`.`ID0`, `t0`.`Almacen_ID`, `t0`.`OrdenDetalles_Color_ID`, `t0`.`OrdenDetalles_Orden_ID`

Include方案执行输出截图


2. Select投影实现

public async Task<IEnumerable<Orden>> GetDetailed()
{
    var ordenes = await _context.Ordenes
                                .Select(o => new Orden
        {
            Id = o.Id,
            Estatus = o.Estatus,
            FechaRegistro = o.FechaRegistro,
            FechaRequerida = o.FechaRequerida,
            Detalles = o.Detalles.Select(d => new OrdenDetalles
            {
                Color = new Color
                {
                    Nombre = d.Color.Nombre
                },
                Tela = new Tela
                {
                    Nombre = d.Tela.Nombre,
                },
                Cantidad = d.Cantidad,
                ProductosAlmacenados = d.ProductosAlmacenados
            }).ToList()
        }
        ).ToListAsync();

    return ordenes;
}

生成的SQL语句

SELECT `o`.`ID`, `o`.`Estatus`, `o`.`Fecha_Registro`, `o`.`Fecha_Requerida`, `t0`.`Nombre`, `t0`.`Nombre0`, `t0`.`Cantidad`, `t0`.`Color_ID`, `t0`.`Orden_ID`, `t0`.`Tela_ID`, `t0`.`ID`, `t0`.`ID0`, `t0`.`Almacen_ID`, `t0`.`OrdenDetalles_Color_ID`, `t0`.`OrdenDetalles_Orden_ID`, `t0`.`OrdenDetalles_Tela_ID`, `t0`.`Cantidad0`
  FROM `orden` AS `o`
  LEFT JOIN (
      SELECT `c`.`Nombre`, `t`.`Nombre` AS `Nombre0`, `o0`.`Cantidad`, `o0`.`Color_ID`, `o0`.`Orden_ID`, `o0`.`Tela_ID`, `c`.`ID`, `t`.`ID` AS `ID0`, `a`.`Almacen_ID`, `a`.`OrdenDetalles_Color_ID`, `a`.`OrdenDetalles_Orden_ID`, `a`.`OrdenDetalles_Tela_ID`, `a`.`Cantidad` AS `Cantidad0`
      FROM `ordendetalles` AS `o0`
      INNER JOIN `color` AS `c` ON `o0`.`Color_ID` = `c`.`ID`
      INNER JOIN `tela` AS `t` ON `o0`.`Tela_ID` = `t`.`ID`
      LEFT JOIN `almacen_productos` AS `a` ON ((`o0`.`Color_ID` = `a`.`OrdenDetalles_Color_ID`) AND (`o0`.`Orden_ID` = `a`.`OrdenDetalles_Orden_ID`)) AND (`o0`.`Tela_ID` = `a`.`OrdenDetalles_Tela_ID`)
  ) AS `t0` ON `o`.`ID` = `t0`.`Orden_ID`
  ORDER BY `o`.`ID`, `t0`.`Color_ID`, `t0`.`Orden_ID`, `t0`.`Tela_ID`, `t0`.`ID`, `t0`.`ID0`, `t0`.`Almacen_ID`, `t0`.`OrdenDetalles_Color_ID`, `t0`.`OrdenDetalles_Orden_ID`

Select方案执行输出截图


相关实体定义

Orden实体

public partial class Orden
{
    public Orden()
    {
        Detalles = new HashSet<OrdenDetalles>();
    }

    public int Id { get; set; }
    public DateTime FechaRegistro { get; set; }
    public DateTime FechaRequerida { get; set; }
    public Estado Estatus { get; set; }

    public virtual ICollection<OrdenDetalles> Detalles { get; set; }
}

OrdenDetalles实体

public partial class OrdenDetalles
{
    public OrdenDetalles()
    {
        ProductosAlmacenados = new HashSet<ProductoAlmacenado>();
    }

    public int ColorId { get; set; }
    public int OrdenId { get; set; }
    public int TelaId { get; set; }
    public int Cantidad { get; set; }

    public virtual Color Color { get; set; }
    public virtual Orden Orden { get; set; }
    public virtual Tela Tela { get; set; }
    public virtual ICollection<ProductoAlmacenado> ProductosAlmacenados { get; set; }
}

结论

  • 不存在比显式Select()投影更优的嵌套导航属性指定字段查询方案。观测到的SQL高度一致完全符合EF Core运行逻辑:两个方案在数据库端的执行效率几乎没有差别,返回字段、关联逻辑、排序规则完全一致,数据库层面的IO、CPU开销基本持平。
  • 应用端性能上Select()方案明确优于Include()方案:
    • Include()会加载被关联实体的所有映射字段,哪怕后续业务根本用不上这些字段,会额外占用内存,同时增加EF Core实体跟踪、对象实例化的开销
    • Select()只会实例化明确声明需要的字段,不会加载多余属性,内存占用更低,对象构造速度更快
    • 注意:当前直接投影到实体类的写法,EF Core不会跟踪这些实体的状态,如果后续要对查询出的实体做更新操作,需要手动附加到上下文;如果是只读查询场景(比如接口返回数据映射DTO),这种写法没有任何问题,还可以搭配AsNoTracking()进一步降低跟踪开销。
  • 如果要继续优化性能,可以在投影时也明确指定ProductosAlmacenados集合下需要的字段,不要直接赋值整个集合,能进一步减少数据库返回的数据量,降低两端开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 19:18:17