EF中如何为EmployeeService统一配置实体Include?策略模式可行吗?
我完全理解你的困扰——每次写查询都要重复粘贴一堆Include代码,不仅冗余还容易漏写或者写错路径,尤其不想用延迟加载的情况下,这个问题确实很烦。下面给你几个实用的解决方案,包括你问到的策略模式,应该能完美解决你的问题:
1. 最直接:封装通用Include扩展方法
把Employee需要的所有导航属性Include逻辑封装成一个扩展方法,这样所有查询都只需要调用这个方法,不用重复写一堆Include。
public static class EmployeeQueryExtensions { // 集中维护Employee需要包含的导航属性 public static IQueryable<Employee> IncludeEmployeeNavigations(this IQueryable<Employee> query) { return query .Include(e => e.EmployeeDepartmentHistory) .Include(e => e.JobCandidate) .Include(e => e.EmployeeDepartmentHistory.Select(his => his.Department)); } }
然后在你的服务类里,查询代码就简化成这样:
public IQueryable<DomainModels.Employee> GetSalariedEmployeesByDepartmentName(string departmentName) { _Database.Configuration.LazyLoadingEnabled = false; return _Database.Employee .IncludeEmployeeNavigations() // 一行搞定所有Include .AsNoTracking() .WhereDepartmentName(departmentName) .WhereSalariedFlag(true) .Select(e => CreateEmployee(e)); }
这个方案的好处是简单高效,所有Include路径集中维护,改一处所有查询都生效,完全避免冗余和错误。
2. 更规范:用Repository模式封装基础查询
如果你的项目规模较大,想把数据访问逻辑和业务服务逻辑分离,Repository模式是更好的选择。把Employee的基础查询(包括Include)放到Repository里,服务类只需要依赖Repository,不用关心底层的Include细节。
首先定义Repository接口和实现:
public interface IEmployeeRepository { // 返回已经包含所有必要导航属性的IQueryable IQueryable<Employee> GetAllWithNavigations(); } public class EmployeeRepository : IEmployeeRepository { private readonly RepoTestEntities1 _dbContext; public EmployeeRepository(RepoTestEntities1 dbContext) { _dbContext = dbContext; _dbContext.Configuration.LazyLoadingEnabled = false; } public IQueryable<Employee> GetAllWithNavigations() { return _dbContext.Employee .Include(e => e.EmployeeDepartmentHistory) .Include(e => e.JobCandidate) .Include(e => e.EmployeeDepartmentHistory.Select(his => his.Department)) .AsNoTracking(); } }
然后修改你的服务类,注入Repository:
public class EmployeeService { private readonly IEmployeeRepository _employeeRepo; // 构造注入Repository public EmployeeService(IEmployeeRepository employeeRepo) { _employeeRepo = employeeRepo; } public IQueryable<DomainModels.Employee> GetSalariedEmployeesByDepartmentName(string departmentName) { return _employeeRepo.GetAllWithNavigations() .WhereDepartmentName(departmentName) .WhereSalariedFlag(true) .Select(e => CreateEmployee(e)); } // 其他服务方法直接调用GetAllWithNavigations即可,不用再写Include }
这个方案符合单一职责原则,数据访问逻辑集中在Repository,服务类专注于业务规则,维护起来更清晰。
3. 灵活进阶:策略模式实现动态Include
你问到的策略模式当然可行!这个方案适合需要动态切换Include组合的场景(比如某些方法只需要部分导航属性,不需要全量Include)。
首先定义Include策略接口:
public interface IEmployeeIncludeStrategy { IQueryable<Employee> ApplyIncludes(IQueryable<Employee> query); }
然后实现不同的策略类,比如默认的全量Include策略:
public class FullEmployeeIncludeStrategy : IEmployeeIncludeStrategy { public IQueryable<Employee> ApplyIncludes(IQueryable<Employee> query) { return query .Include(e => e.EmployeeDepartmentHistory) .Include(e => e.JobCandidate) .Include(e => e.EmployeeDepartmentHistory.Select(his => his.Department)); } }
如果有某个方法只需要Include部门历史,就新增一个策略:
public class DepartmentHistoryOnlyStrategy : IEmployeeIncludeStrategy { public IQueryable<Employee> ApplyIncludes(IQueryable<Employee> query) { return query.Include(e => e.EmployeeDepartmentHistory); } }
最后修改服务类,注入或者动态选择策略:
public class EmployeeService { private readonly RepoTestEntities1 _database; private readonly IEmployeeIncludeStrategy _defaultIncludeStrategy; // 构造注入默认策略,也可以按需传入不同策略 public EmployeeService(RepoTestEntities1 database, IEmployeeIncludeStrategy defaultStrategy = null) { _database = database; _database.Configuration.LazyLoadingEnabled = false; _defaultIncludeStrategy = defaultStrategy ?? new FullEmployeeIncludeStrategy(); } public IQueryable<DomainModels.Employee> GetSalariedEmployeesByDepartmentName(string departmentName) { // 使用默认策略 return _defaultIncludeStrategy.ApplyIncludes(_database.Employee) .AsNoTracking() .WhereDepartmentName(departmentName) .WhereSalariedFlag(true) .Select(e => CreateEmployee(e)); } // 某个需要特殊Include的方法,动态使用其他策略 public IQueryable<DomainModels.Employee> GetEmployeesWithOnlyDepartmentHistory() { var strategy = new DepartmentHistoryOnlyStrategy(); return strategy.ApplyIncludes(_database.Employee) .AsNoTracking() .Select(e => CreateEmployee(e)); } }
这个方案的优势是高度灵活,当需要新增不同的Include组合时,只需要新增策略类,不用修改现有代码,完全符合开放封闭原则。
内容的提问来源于stack exchange,提问作者inrandomwetrust

