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

EF中Include/ThenInclude与可空导航属性的实践疑问及优化咨询

EF Core 导航属性为空时使用ThenInclude的疑问

我写了一段数据查询代码,为了在Clinic导航属性为空时仍能调用ThenInclude(),用了空合并运算符??创建虚拟Clinic对象替代,但我觉得这个写法不太好,也不确定它是否有效。我的问题如下:

  1. 在Include()内部使用new()在当前及未来EF版本中是否为良好/安全的实践?
  2. 在ThenInclude()内部使用new()在当前及未来EF版本中是否为良好/安全的实践?
  3. 是否有更优写法,无需拆分多查询或延迟加载clinic!.Base与clinic!相关属性?

注:返回实体为只读,无变更追踪。CreateBaseQueryable方法仅返回DbContext的IQueryable对象,此处未提供其代码。

List<Child> children = await CreateBaseQueryable(context, healthBoardId)
    .Include(child => child.ChildStatuses)
    .Include(child => child.ChildClinics)
        .ThenInclude(childClinic => childClinic.Clinic ?? new Clinic())
            .ThenInclude(clinic => clinic!.Base)
    .Include(child => child.ChildClinics)
        .ThenInclude(childClinic => childClinic.Clinic ?? new Clinic())
            .ThenInclude(clinic => clinic!.Board)
    .Where(child => child.ChildStatuses.Any(status => status.ACounter >= 2))
    .Where(child => child.ChildClinics.Any(
        clinic => !clinic.IsDeleted &&
        clinic.Clinic != null &&
        (
          clinic.Clinic.ClinicType == (int)ClinicType.Type1||
          clinic.Clinic.ClinicType == (int)ClinicType.Type2)
        )
     )
     .ToListAsync();

问题解答

1. Include()内部使用new()的实践安全性

这绝对不是EF Core推荐的做法。Include的作用是定义数据库关联数据的加载路径,本质是告诉EF要生成包含关联表的SQL查询。而new()创建的是内存对象,EF无法将它映射到数据库的关联关系上,这种写法属于未定义行为——当前版本可能碰巧能运行,但后续EF版本的查询解析逻辑一旦调整,大概率会出现SQL生成错误或查询结果异常,完全没有安全性可言。

2. ThenInclude()内部使用new()的实践安全性

和上面的问题本质一致,ThenInclude是基于前面的关联路径继续加载深层关联,它需要明确知道当前路径对应的数据库实体和外键关系。虚拟的new Clinic()无法提供这些元数据,EF在解析时可能会出现逻辑混乱,要么生成无效SQL,要么实际根本没加载到Base/Board数据。同样属于不安全、不规范的写法,存在严重的版本兼容性风险。

3. 更优写法推荐

你的查询已经通过Where条件过滤掉了clinic.Clinic == null的记录(childClinic.Clinic != null),所以所有符合条件的ChildClinics的Clinic属性肯定不为空。基于这个前提,完全可以去掉空合并运算符,直接用空值抑制运算符!告诉编译器“我确定这个属性不为空”:

List<Child> children = await CreateBaseQueryable(context, healthBoardId)
    .Include(child => child.ChildStatuses)
    .Include(child => child.ChildClinics)
        .ThenInclude(childClinic => childClinic.Clinic!)
            .ThenInclude(clinic => clinic.Base)
    .Include(child => child.ChildClinics)
        .ThenInclude(childClinic => childClinic.Clinic!)
            .ThenInclude(clinic => clinic.Board)
    .Where(child => child.ChildStatuses.Any(status => status.ACounter >= 2))
    .Where(child => child.ChildClinics.Any(
        clinic => !clinic.IsDeleted &&
        clinic.Clinic != null &&
        (
          clinic.Clinic.ClinicType == (int)ClinicType.Type1||
          clinic.Clinic.ClinicType == (int)ClinicType.Type2)
        )
     )
     .AsNoTracking() // 明确指定无变更追踪,符合只读需求
     .ToListAsync();

这种写法完全符合EF Core的查询规范,没有兼容性风险,也不需要拆分查询或启用延迟加载。如果担心后续Where条件被修改导致Clinic为空的情况漏进来,还可以在Include时直接过滤ChildClinics:

.Include(child => child.ChildClinics.Where(cc => cc.Clinic != null))
    .ThenInclude(childClinic => childClinic.Clinic!)
        .ThenInclude(clinic => clinic.Base)

这样能确保加载的ChildClinics都是Clinic不为空的,进一步规避空引用风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:55:11