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

C# EF数据库查询优化:新写法无法获取完整数据求解决方案

问题:EF Core查询优化后数据缺失的修复与高效实现方案

我从后端获取某任务及其关联信息,数据量仅40KB,但原注释代码耗时5-6秒。于是改用当前未注释的代码实现,原注释代码可获取全部所需数据,但新写法却无法做到,逻辑上二者应一致。求该问题的修复方案,或更高效的查询实现方式。

public Task GetTask(int taskId)
{
    // return Context.Tasks
    //     .Include(a => a.Customer)
    //     .Include(a => a.Project)
    //     .Include(a => a.Person)
    //     .Include(a => a.TaskSubs.OrderBy(b => b.Done).ThenBy(b => b.Order))
    //     .Include(a => a.TaskStatus)
    //     .Include(a => a.TaskPriority)
    //     .Include(a => a.TaskEmployees.Where(b => b.Active == true))
    //     .Include(a => a.TaskComments.OrderByDescending(b => b.CreatedOn))
    //     .Include(a => a.TaskCommentCustomers.OrderByDescending(b => b.CreatedOn))
    //     .ThenInclude(b => b.Person)
    //     .Include(a => a.TaskAppointments.OrderByDescending(b => b.Appointment.Date).Where(a => a.Appointment.Active))
    //     .ThenInclude(b => b.Appointment)
    //     .Include(a => a.TaskProjectices)
    //     .ThenInclude(b => b.ProjectEx)
    //     .Single(a => a.TaskId == taskId);
    
    var task = Context.Tasks
        .AsNoTracking()
        .Include(a => a.Customer)
        .Include(a => a.Project)
        .Include(a => a.Person)
        .Include(a => a.TaskSubs)
        .Include(a => a.TaskStatus)
        .Include(a => a.TaskEmployees.Where(b => b.Active == true))
        .Include(a => a.TaskComments)
        .Include(a => a.TaskCommentCustomers)
        .ThenInclude(b => b.Person)
        .Include(a => a.TaskAppointments)
        .ThenInclude(b => b.Appointment)
        .Include(a => a.TaskProjectices)
        .ThenInclude(b => b.ProjectEx)
        .SingleOrDefault(a => a.TaskId == taskId);


    task.TaskComments = task.TaskComments.OrderByDescending(b => b.CreatedOn).ToList();
    task.TaskCommentCustomers = task.TaskCommentCustomers.OrderByDescending(b => b.CreatedOn).ToList();
    task.TaskAppointments = task.TaskAppointments.OrderByDescending(b => b.Appointment.Date).Where(a => a.Appointment.Active).ToList();
    task.TaskSubs = task.TaskSubs.OrderBy(b => b.Done).ThenBy(b => b.Order).ToList();
    
    return task;
}

问题原因分析

  1. 缺失关联数据Include:新代码漏掉了原代码中的Include(a => a.TaskPriority),直接导致TaskPriority关联数据无法加载。
  2. 集合处理的空引用风险:如果数据库中不存在某类关联数据(比如无评论),直接调用OrderBy会触发空引用异常,导致数据组装失败。
  3. 返回逻辑差异:原代码用Single确保任务存在时返回实体,不存在则抛异常;新代码用SingleOrDefault会返回null,若未处理null会导致后续操作失效。

修复方案

  1. 补上缺失的Include:在新代码的Include链中添加Include(a => a.TaskPriority),确保所有关联数据都被加载:
    var task = Context.Tasks
        .AsNoTracking()
        .Include(a => a.Customer)
        .Include(a => a.Project)
        .Include(a => a.Person)
        .Include(a => a.TaskSubs)
        .Include(a => a.TaskStatus)
        .Include(a => a.TaskPriority) // 补上这一行
        .Include(a => a.TaskEmployees.Where(b => b.Active == true))
        // ... 其他Include保持不变
        .SingleOrDefault(a => a.TaskId == taskId);
    
  2. 处理集合空引用:在对集合排序/过滤时先判空,避免异常:
    task.TaskComments = task.TaskComments?.OrderByDescending(b => b.CreatedOn).ToList() ?? new List<TaskComment>();
    task.TaskCommentCustomers = task.TaskCommentCustomers?.OrderByDescending(b => b.CreatedOn).ToList() ?? new List<TaskCommentCustomer>();
    task.TaskAppointments = task.TaskAppointments?.OrderByDescending(b => b.Appointment.Date).Where(a => a.Appointment.Active).ToList() ?? new List<TaskAppointment>();
    task.TaskSubs = task.TaskSubs?.OrderBy(b => b.Done).ThenBy(b => b.Order).ToList() ?? new List<TaskSub>();
    
  3. 统一返回逻辑:如果业务上taskId必然存在,换回Single避免返回null;若允许不存在,需在返回前判断null并处理(比如返回默认值或抛业务异常):
    // 若taskId必存在
    var task = Context.Tasks
        // ... Include链
        .Single(a => a.TaskId == taskId);
    

更高效的查询实现方式

1. 投影到DTO(推荐)

避免加载实体的全部字段,只查询业务所需数据,减少数据库传输和内存占用:

public class TaskDto
{
    public int TaskId { get; set; }
    // 主任务其他所需属性
    public CustomerDto Customer { get; set; }
    public List<TaskSubDto> TaskSubs { get; set; }
    // 其他关联数据的DTO
}

public TaskDto GetTask(int taskId)
{
    return Context.Tasks
        .Where(t => t.TaskId == taskId)
        .Select(t => new TaskDto
        {
            TaskId = t.TaskId,
            // 映射主任务属性
            Customer = new CustomerDto { Id = t.Customer.Id, Name = t.Customer.Name },
            TaskSubs = t.TaskSubs.OrderBy(s => s.Done).ThenBy(s => s.Order)
                                 .Select(s => new TaskSubDto { /* 映射属性 */ }).ToList(),
            // 其他关联数据同理映射
        })
        .SingleOrDefault();
}

这种方式EF会生成精准的SQL,只查询需要的列,性能远优于全实体加载。

2. 分批次查询关联数据

当关联集合较多时,避免一次性Include导致的笛卡尔积问题,分开查询主任务和关联数据:

public Task GetTask(int taskId)
{
    var task = Context.Tasks
        .AsNoTracking()
        .Include(a => a.Customer)
        .Include(a => a.Project)
        .Include(a => a.Person)
        .Include(a => a.TaskStatus)
        .Include(a => a.TaskPriority)
        .Single(a => a.TaskId == taskId);

    // 单独查询关联数据
    task.TaskSubs = Context.TaskSubs
        .Where(s => s.TaskId == taskId)
        .OrderBy(s => s.Done).ThenBy(s => s.Order)
        .ToList();
    
    task.TaskComments = Context.TaskComments
        .Where(c => c.TaskId == taskId)
        .OrderByDescending(c => c.CreatedOn)
        .ToList();
    
    // 其他关联数据同理查询
    
    return task;
}

此方式减少了复杂Join的开销,尤其当多个关联集合数据量较大时性能提升明显。

3. 数据库索引优化

为查询中用到的过滤、排序字段添加索引:

  • 关联表的TaskId字段(如TaskSubs.TaskId、TaskComments.TaskId)
  • TaskAppointments.Appointment.Active和Appointment.Date组合索引
  • TaskComments.CreatedOn、TaskCommentCustomers.CreatedOn字段索引

索引能大幅降低数据库查询的IO开销,从根源提升性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 10:17:55