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; }
问题原因分析
- 缺失关联数据Include:新代码漏掉了原代码中的
Include(a => a.TaskPriority),直接导致TaskPriority关联数据无法加载。 - 集合处理的空引用风险:如果数据库中不存在某类关联数据(比如无评论),直接调用
OrderBy会触发空引用异常,导致数据组装失败。 - 返回逻辑差异:原代码用
Single确保任务存在时返回实体,不存在则抛异常;新代码用SingleOrDefault会返回null,若未处理null会导致后续操作失效。
修复方案
- 补上缺失的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); - 处理集合空引用:在对集合排序/过滤时先判空,避免异常:
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>(); - 统一返回逻辑:如果业务上
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
相关产品推荐
相关产品推荐

