在企业项目中,随着业务不断迭代,数据查询往往会越来越复杂。例如,一个 HR 系统最开始可能只有一个需求:根据部门查询员工。仓储类可能长这样:
public IEnumerable<Employee> GetByDepartment(int departmentId) { return _dbContext.Employees .Where(e => e.DepartmentId == departmentId) .ToList(); }后来产品又提出新的需求:按年龄查询、按薪资范围查询、按国籍查询、按部门 + 年龄查询、按部门 + 国籍查询、按部门 + 年龄 + 薪资查询……如果继续沿用这种方式,仓储类很快就会变成一堆方法的集合。随着筛选条件越来越多,方法数量几乎呈指数增长,仓储类最终会变得越来越臃肿,也越来越难维护。
那么,有没有一种方式,既能保持查询的灵活性,又不用为每种组合都写一个方法?
第一次尝试:直接返回 IQueryable
很多人在接触 EF Core 后,都会想到一个简单的办法:把 IQueryable<T> 返回出去。
public IQueryable<Employee> Query() { return _dbContext.Employees; }这里返回的并不是已经查询好的数据,而是一棵尚未执行的查询表达式树。调用方可以根据自己的需要继续拼接条件:
var employee = _repo.Query() .Where(e => e.Id == id) .FirstOrDefault();或者:
var employees = _repo.Query() .Where(e => e.DepartmentId == departmentId) .OrderBy(e => e.Name) .ToList();这种方式最大的优点就是灵活。无论以后增加多少筛选条件,都不用继续增加新的仓储方法。但是,它也存在一个明显的问题:业务层开始到处写 .Where(...)、.OrderBy(...)、.Include(...)、.Select(...),查询逻辑分散在各个业务模块里。时间久了以后,同样的查询条件可能在多个地方重复出现,维护起来反而越来越困难。
因此,我们通常会进一步把这些公共查询提取出来。
使用查询扩展(Query Extensions)组织查询逻辑
一种比较简单也比较常见的做法,就是把常用查询封装成扩展方法。
public staticclassEmployeeQueryExtensions { public static IQueryable<Employee> ById( this IQueryable<Employee> query, &nb