EF Core规范模式:封装动态查询逻辑,提升代码可维护性与可测试性
这次我们来看一个在 .NET 领域,特别是使用 EF Core 进行数据访问时,如何提升代码质量与可维护性的核心模式:规范模式。如果你在项目中写过大量包含复杂Where、OrderBy、Include的动态查询,并且发现这些查询逻辑散落在各个服务层或控制器中,难以复用和测试,那么这个模式就是为你准备的。
规范模式的核心目标不是引入新功能,而是解决查询逻辑的“脏乱差”问题。它通过将查询条件、排序规则、分页逻辑等封装成独立的、可组合的“规范”对象,让查询代码变得清晰、可复用且易于测试。最直接的好处是,你可以告别那些动辄几十行、包含多个if判断的查询方法,转而使用声明式的、像乐高积木一样可以拼装的查询组件。
本文将带你从零开始,理解规范模式在 EF Core 中的落地。我们会重点关注它的实现原理(特别是表达式树的构建与组合)、如何在实际项目中应用、以及它带来的性能与维护性优势。无论你是正在为查询代码膨胀而烦恼,还是希望构建更健壮的数据访问层,这篇文章都能提供一套清晰的解决方案。
1. 核心能力速览
在深入代码之前,我们先通过一个表格快速了解规范模式能为你的 EF Core 项目带来什么:
| 能力项 | 说明 |
|---|---|
| 核心目标 | 封装与复用查询逻辑,实现关注点分离,提升代码可测试性。 |
| 技术本质 | 利用 C# 表达式树动态构建和组合IQueryable<T>查询。 |
| 主要功能 | 1. 封装查询条件(Where)2. 封装排序规则( OrderBy/ThenBy)3. 封装数据转换( Select/Include)4. 支持逻辑组合(与、或、非)。 |
| 启动/集成方式 | 非独立服务,是一种设计模式。通过创建ISpecification<T>接口及实现类,集成到仓储层或查询服务中。 |
| 性能影响 | 零额外开销。规范在编译时构建表达式树,EF Core 仍将其转换为单条最优 SQL,不会导致IEnumerable内存过滤。 |
| 适合场景 | 业务查询条件复杂多变、查询逻辑需要多处复用、追求高可测试性的中大型项目。 |
| 不适合场景 | 极其简单的 CRUD 项目(可能过度设计),或查询逻辑完全静态、无需组合的场景。 |
2. 适用场景与使用边界
规范模式并非银弹,理解其适用边界能帮助你做出正确的技术选型。
它非常适合以下场景:
- 动态查询构建:用户通过前端界面组合多种过滤条件(如状态、时间范围、关键词)进行查询。传统写法需要在后端用大量
if语句拼接IQueryable,而规范模式可以将每个条件封装成一个规范,然后灵活组合。 - 查询逻辑复用:同一个查询条件(如“获取已发布的文章”)在后台管理列表和前台展示接口中都需要使用。将其封装为
PublishedPostsSpecification,即可避免代码重复。 - 复杂业务规则:查询逻辑本身很复杂,涉及多个表的关联和条件判断。将其封装后,业务服务层的代码可以更专注于业务流程,而非数据访问细节。
- 单元测试友好:由于查询逻辑被封装在规范对象中,你可以轻松地对规范本身进行单元测试,验证其表达式树是否正确构建,而无需连接真实数据库。
它的使用边界与注意事项:
- 不替代基础仓储:规范模式通常与泛型仓储或查询服务结合使用,它封装的是“怎么查”,而仓储定义的是“对谁查”的基础接口。
- 复杂度权衡:对于只有一两个固定查询的小型项目,引入规范模式可能会增加不必要的抽象层次。评估投入产出比是关键。
- 理解表达式树:要实现强大的规范,需要对 C# 表达式树有基本了解。这是实现逻辑组合(与、或)的技术基础。
- EF Core 特性兼容:确保你构建的表达式树能被 EF Core 的查询提供程序正确转换。某些复杂的 C# 函数在数据库端可能没有直接对应项。
3. 环境准备与前置条件
在开始编码实现之前,请确保你的开发环境满足以下要求。规范模式不依赖特定外部库,核心是语言和框架特性。
开发环境与 SDK
- 操作系统:Windows 10/11, macOS 或 Linux 发行版均可。
- .NET SDK:需要 .NET 6.0 或更高版本。推荐使用最新的长期支持版本(如 .NET 8.0),以获取最佳性能和语言特性支持。可通过
dotnet --version命令检查。
集成开发环境
- Visual Studio 2022(推荐):使用 17.0 或更高版本,确保对 C# 新特性和 EF Core 工具的良好支持。
- Visual Studio Code:安装 C# 扩展包即可,轻量且高效。
项目类型与依赖
- 一个基于 EF Core 的 ASP.NET Core Web API、MVC 或类库项目。
- NuGet 包:确保已安装
Microsoft.EntityFrameworkCore和对应数据库提供程序(如Microsoft.EntityFrameworkCore.SqlServer)。规范模式本身不依赖特定版本。
<!-- 项目文件 (.csproj) 中的典型引用 --> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" PrivateAssets="All" /> </ItemGroup>知识准备
- C# 中级:熟练掌握
IQueryable<T>、Func<T, bool>委托与Expression<Func<T, bool>>表达式树的区别至关重要。 - EF Core 基础:了解基本的
DbSet查询、LINQ 语法和如何执行查询。 - 基础领域模型:我们将使用一个简单的
Product(产品)实体作为示例。
- C# 中级:熟练掌握
4. 规范模式的核心实现
让我们从最核心的接口和基类开始构建。这是整个模式的骨架。
4.1 定义规范接口
首先,定义一个最基础的规范接口。它的核心是提供一个Criteria属性,即查询的“条件”,其类型是表达式树。
// ISpecification.cs using System.Linq.Expressions; namespace YourProject.Specifications { public interface ISpecification<T> { // 核心:查询条件表达式树 Expression<Func<T, bool>>? Criteria { get; } // 可选的:包含的导航属性(用于 Include) List<Expression<Func<T, object>>> Includes { get; } // 可选的:排序表达式 Expression<Func<T, object>>? OrderBy { get; } Expression<Func<T, object>>? OrderByDescending { get; } // 可选的:分页属性 int Take { get; } int Skip { get; } bool IsPagingEnabled { get; } } }4.2 实现基础规范基类
接下来,创建一个抽象的基类来实现这个接口,并提供一些构建方法。这可以避免在每个具体规范中重复实现属性。
// BaseSpecification.cs using System.Linq.Expressions; namespace YourProject.Specifications { public abstract class BaseSpecification<T> : ISpecification<T> { // 实现接口属性 public Expression<Func<T, bool>>? Criteria { get; private set; } public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>(); public Expression<Func<T, object>>? OrderBy { get; private set; } public Expression<Func<T, object>>? OrderByDescending { get; private set; } public int Take { get; private set; } public int Skip { get; private set; } public bool IsPagingEnabled { get; private set; } // 保护方法,供派生类在构造函数中调用以构建规范 protected void ApplyCriteria(Expression<Func<T, bool>> criteria) { Criteria = criteria; } protected void AddInclude(Expression<Func<T, object>> includeExpression) { Includes.Add(includeExpression); } protected void ApplyOrderBy(Expression<Func<T, object>> orderByExpression) { OrderBy = orderByExpression; } protected void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression) { OrderByDescending = orderByDescendingExpression; } protected void ApplyPaging(int skip, int take) { Skip = skip; Take = take; IsPagingEnabled = true; } } }4.3 创建规约评估器(核心)
这是将规范应用到IQueryable的关键组件。它接收一个规范对象,并依次将条件、包含、排序、分页等应用到查询上。
// SpecificationEvaluator.cs using Microsoft.EntityFrameworkCore; namespace YourProject.Specifications { public class SpecificationEvaluator<T> where T : class { public static IQueryable<T> GetQuery(IQueryable<T> inputQuery, ISpecification<T> specification) { var query = inputQuery; // 1. 应用过滤条件 (Where) if (specification.Criteria != null) { query = query.Where(specification.Criteria); } // 2. 应用包含关系 (Include) - 将多个 Include 聚合 query = specification.Includes.Aggregate(query, (current, include) => current.Include(include)); // 3. 应用排序 (OrderBy) if (specification.OrderBy != null) { query = query.OrderBy(specification.OrderBy); } else if (specification.OrderByDescending != null) { query = query.OrderByDescending(specification.OrderByDescending); } // 4. 应用分页 (Skip/Take) if (specification.IsPagingEnabled) { query = query.Skip(specification.Skip).Take(specification.Take); } return query; } } }5. 在仓储层中应用规范
现在,我们需要一个地方来使用这个评估器。通常,我们会扩展泛型仓储或创建一个专门的查询服务。
5.1 扩展泛型仓储接口与实现
假设你有一个基础的泛型仓储接口IRepository<T>。
// IRepository.cs (扩展后) using System.Linq.Expressions; namespace YourProject.Core.Interfaces { public interface IRepository<T> where T : class { Task<T?> GetByIdAsync(int id); Task<IReadOnlyList<T>> ListAllAsync(); // 新增:使用规范进行查询 Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec); Task<int> CountAsync(ISpecification<T> spec); Task<T?> FirstOrDefaultAsync(ISpecification<T> spec); // ... 其他 Add, Update, Delete 方法 } }其 EF Core 实现类需要注入DbContext,并使用SpecificationEvaluator。
// EfRepository.cs using Microsoft.EntityFrameworkCore; using YourProject.Core.Interfaces; using YourProject.Specifications; namespace YourProject.Infrastructure.Data { public class EfRepository<T> : IRepository<T> where T : class { protected readonly DbContext _dbContext; public EfRepository(DbContext dbContext) { _dbContext = dbContext; } public virtual async Task<T?> GetByIdAsync(int id) { return await _dbContext.Set<T>().FindAsync(id); } public virtual async Task<IReadOnlyList<T>> ListAllAsync() { return await _dbContext.Set<T>().ToListAsync(); } // 核心方法:应用规范进行查询 public virtual async Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec) { return await ApplySpecification(spec).ToListAsync(); } public virtual async Task<int> CountAsync(ISpecification<T> spec) { return await ApplySpecification(spec).CountAsync(); } public virtual async Task<T?> FirstOrDefaultAsync(ISpecification<T> spec) { return await ApplySpecification(spec).FirstOrDefaultAsync(); } // 私有方法:构建应用了规范的 IQueryable private IQueryable<T> ApplySpecification(ISpecification<T> spec) { return SpecificationEvaluator<T>.GetQuery(_dbContext.Set<T>().AsQueryable(), spec); } } }6. 功能测试与效果验证:从理论到实践
让我们通过一个完整的业务场景来验证规范模式如何工作。假设我们有一个Product实体。
6.1 定义领域实体与上下文
// Product.cs namespace YourProject.Core.Entities { public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public decimal Price { get; set; } public bool IsActive { get; set; } public DateTime CreatedDate { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } = null!; // 导航属性 } public class Category { public int Id { get; set; } public string Name { get; set; } = string.Empty; public ICollection<Product> Products { get; set; } = new List<Product>(); } }// AppDbContext.cs using Microsoft.EntityFrameworkCore; using YourProject.Core.Entities; namespace YourProject.Infrastructure.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Product> Products => Set<Product>(); public DbSet<Category> Categories => Set<Category>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { // 这里可以配置实体关系、索引等 base.OnModelCreating(modelBuilder); } } }6.2 创建具体业务规范
现在,创建可复用的规范类。这是模式威力展现的地方。
场景一:获取所有活跃产品
// ActiveProductsSpecification.cs using System.Linq.Expressions; using YourProject.Core.Entities; using YourProject.Specifications; namespace YourProject.Core.Specifications { public class ActiveProductsSpecification : BaseSpecification<Product> { public ActiveProductsSpecification() { // 构建查询条件:IsActive == true ApplyCriteria(p => p.IsActive); // 默认按创建日期降序排序 ApplyOrderByDescending(p => p.CreatedDate); } } }场景二:根据关键词搜索产品,并包含分类信息
// ProductsWithCategoryAndSearchSpecification.cs using System.Linq.Expressions; using YourProject.Core.Entities; using YourProject.Specifications; namespace YourProject.Core.Specifications { public class ProductsWithCategoryAndSearchSpecification : BaseSpecification<Product> { public ProductsWithCategoryAndSearchSpecification(string? searchTerm) { // 1. 包含导航属性 AddInclude(p => p.Category); // 2. 动态构建搜索条件 if (!string.IsNullOrWhiteSpace(searchTerm)) { var searchTermToLower = searchTerm.ToLower(); // 在名称或描述中搜索 ApplyCriteria(p => p.Name.ToLower().Contains(searchTermToLower) || p.Description.ToLower().Contains(searchTermToLower)); } // 3. 默认按价格升序排序 ApplyOrderBy(p => p.Price); } } }场景三:复杂分页查询(价格区间 + 特定分类)
// ProductsByPriceRangeAndCategorySpecification.cs using System.Linq.Expressions; using YourProject.Core.Entities; using YourProject.Specifications; namespace YourProject.Core.Specifications { public class ProductsByPriceRangeAndCategorySpecification : BaseSpecification<Product> { public ProductsByPriceRangeAndCategorySpecification(decimal minPrice, decimal maxPrice, int? categoryId, int pageIndex, int pageSize) { // 构建条件:价格在区间内,且(如果提供了分类ID)属于该分类 ApplyCriteria(p => p.Price >= minPrice && p.Price <= maxPrice && (!categoryId.HasValue || p.CategoryId == categoryId.Value)); AddInclude(p => p.Category); ApplyOrderByDescending(p => p.CreatedDate); // 应用分页 ApplyPaging((pageIndex - 1) * pageSize, pageSize); } } }6.3 在应用服务中使用规范
最后,在服务层(如ProductService)中,注入仓储并使用这些规范。
// ProductService.cs using YourProject.Core.Entities; using YourProject.Core.Interfaces; using YourProject.Core.Specifications; namespace YourProject.Core.Services { public class ProductService { private readonly IRepository<Product> _productRepository; public ProductService(IRepository<Product> productRepository) { _productRepository = productRepository; } public async Task<IReadOnlyList<Product>> GetActiveProductsAsync() { var spec = new ActiveProductsSpecification(); return await _productRepository.ListAsync(spec); } public async Task<(IReadOnlyList<Product> Items, int TotalCount)> SearchProductsAsync(string? keyword, int pageIndex, int pageSize) { var spec = new ProductsWithCategoryAndSearchSpecification(keyword); // 先获取总数(用于分页UI) var totalItems = await _productRepository.CountAsync(spec); // 创建分页规范(注意:这里演示了规范组合的思想,实际中可能创建一个集成了搜索和分页的规范更合适) var pagedSpec = new ProductsWithCategoryAndSearchSpecification(keyword); // 假设我们在基类或一个新规范中处理分页逻辑 // 这里为了演示,我们使用一个更完整的规范 var pagedSpecWithSearch = new ProductsByPriceRangeAndCategorySpecification(0, decimal.MaxValue, null, pageIndex, pageSize); // 注意:上面的例子不完美,理想情况是规范支持更灵活的组合。这引出了下一个高级话题:规范组合。 var items = await _productRepository.ListAsync(pagedSpecWithSearch); return (items, totalItems); } } }效果验证:
- 代码清晰度:服务层方法变得极其简洁,业务意图一目了然(“获取活跃产品”、“搜索产品”)。
- 复用性:
ActiveProductsSpecification可以在后台管理、前台API等多个地方使用。 - 可测试性:你可以对
ActiveProductsSpecification进行单元测试,验证其Criteria表达式是否正确,而无需启动数据库。 - SQL 生成:打开 EF Core 的日志,你会看到最终生成的 SQL 是单条语句,包含了所有
WHERE、JOIN(对应Include)、ORDER BY和OFFSET FETCH子句,性能无损。
7. 高级主题:规范组合与逻辑运算
基础规范解决了封装问题,但真正的威力在于组合。我们如何实现“条件A且条件B”或“条件A或条件B”?
这需要更高级的规范实现,能够操作表达式树本身。以下是实现逻辑“与”和“或”的组合器示例。
// SpecificationExtensions.cs using System.Linq.Expressions; using YourProject.Specifications; namespace YourProject.Core.Specifications { public static class SpecificationExtensions { // 组合两个规范,用 AND 连接 public static ISpecification<T> And<T>(this ISpecification<T> left, ISpecification<T> right) { return new AndSpecification<T>(left, right); } // 组合两个规范,用 OR 连接 public static ISpecification<T> Or<T>(this ISpecification<T> left, ISpecification<T> right) { return new OrSpecification<T>(left, right); } // 取反一个规范 public static ISpecification<T> Not<T>(this ISpecification<T> spec) { return new NotSpecification<T>(spec); } } // 内部实现类:AND 组合规范 internal class AndSpecification<T> : BaseSpecification<T> { public AndSpecification(ISpecification<T> left, ISpecification<T> right) { // 关键:使用 Expression.AndAlso 组合两个表达式树 var paramExpr = Expression.Parameter(typeof(T)); var leftExpr = left.Criteria; var rightExpr = right.Criteria; if (leftExpr != null && rightExpr != null) { var combinedBody = Expression.AndAlso( Expression.Invoke(leftExpr, paramExpr), Expression.Invoke(rightExpr, paramExpr) ); var combinedExpr = Expression.Lambda<Func<T, bool>>(combinedBody, paramExpr); ApplyCriteria(combinedExpr); } else if (leftExpr != null) { ApplyCriteria(leftExpr); } else if (rightExpr != null) { ApplyCriteria(rightExpr); } // 合并 Includes, OrderBy 等(简化处理,实际可能需要更复杂的合并逻辑) Includes.AddRange(left.Includes); Includes.AddRange(right.Includes); // 注意:排序和分页在组合时通常以最后一个或某个特定规范为准,需要根据业务定义 } } // OR 和 NOT 规范的实现类似,使用 Expression.OrElse 和 Expression.Not internal class OrSpecification<T> : BaseSpecification<T> { /* 实现略 */ } internal class NotSpecification<T> : BaseSpecification<T> { /* 实现略 */ } }使用组合规范:
// 在服务中组合使用 public async Task<IReadOnlyList<Product>> GetComplexQueryResultsAsync() { var activeSpec = new ActiveProductsSpecification(); var expensiveSpec = new BaseSpecification<Product>(); expensiveSpec.ApplyCriteria(p => p.Price > 1000); // 假设我们动态构建了一个高价产品规范 // 组合:获取活跃且高价的产品 var combinedSpec = activeSpec.And(expensiveSpec); // 或者:获取活跃或高价的产品 // var combinedSpec = activeSpec.Or(expensiveSpec); return await _productRepository.ListAsync(combinedSpec); }8. 资源占用与性能观察
规范模式本身是编译时的代码组织模式,不引入任何运行时性能开销。性能关键点在于你如何使用它以及 EF Core 如何翻译它。
- 表达式树 vs 委托:确保规范中的
Criteria是Expression<Func<T, bool>>而不是Func<T, bool>。前者允许 EF Core 分析并转换为 SQL,后者会导致客户端内存过滤,性能极差。 - Include 的谨慎使用:在规范中滥用
AddInclude会导致 SQL 产生复杂的JOIN,可能影响查询速度。只包含当前查询真正需要的导航属性。 - 分页在数据库端进行:通过规范的
ApplyPaging方法,分页逻辑(Skip/Take)会被推送到数据库执行,这是高效的。避免先ToList()再在内存中分页。 - 组合规范的表达式复杂度:深度嵌套的
And/Or组合可能会生成复杂的表达式树,但 EF Core 通常能很好地处理。对于极端复杂的动态查询,需关注生成的 SQL 语句是否最优。 - 监控工具:使用 EF Core 的
LogTo控制台日志或像MiniProfiler这样的工具,来监控规范最终生成的 SQL 语句和执行时间,这是性能调优的最佳实践。
9. 常见问题与排查方法
在实现和使用规范模式时,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 查询返回所有数据,过滤条件未生效 | 1. 规范的Criteria属性为null。2. ApplyCriteria方法未被调用或调用时机不对。 | 1. 调试检查具体规范实例的Criteria属性值。2. 查看规范类构造函数或构建方法。 | 确保在规范构造函数或初始化方法中正确调用了ApplyCriteria。 |
出现InvalidOperationException: 无法翻译 LINQ 表达式 | 规范中的表达式包含了无法转换为 SQL 的 C# 方法或逻辑。 | 1. 检查Criteria或OrderBy表达式中的方法调用(如某些字符串处理函数)。2. 查看 EF Core 异常详情。 | 1. 使用 EF Core 支持的函数(如string.Contains)。2. 将无法翻译的逻辑拆分,部分在数据库查询后于内存中处理。 |
Include未生效,导航属性为null | 1.AddInclude方法未被调用。2. 在 Select投影后调用Include是无效的。 | 1. 检查规范中Includes集合是否包含预期的表达式。2. 确保查询构建顺序正确。 | 在规范中正确添加Include表达式。确保SpecificationEvaluator中Include在Select之前应用。 |
| 组合规范(And/Or)时出错 | 表达式树组合逻辑有误,例如参数未正确替换。 | 1. 使用调试器查看组合后的表达式树结构。 2. 编写单元测试验证组合规范生成的 Criteria。 | 仔细检查AndSpecification/OrSpecification中Expression.Invoke和参数重绑的逻辑。参考成熟的库(如 Ardalis.Specification)的实现。 |
| 分页结果不正确 | 1.Skip和Take值计算错误。2. 排序规则不明确,导致分页数据不稳定。 | 1. 检查ApplyPaging方法传入的参数。2. 确保在分页前应用了确定的排序规则。 | 1. 确认分页公式:Skip = (pageIndex - 1) * pageSize。2.分页前必须排序,在规范中总是设置 OrderBy或OrderByDescending。 |
使用异步方法(如ToListAsync)时编译错误 | 规范模式返回的IQueryable可能在某些上下文中无法识别异步扩展方法。 | 确认调用ListAsync的代码文件已正确引用Microsoft.EntityFrameworkCore命名空间。 | 在仓储或服务层的方法上使用async/await,并确保引用了Microsoft.EntityFrameworkCore。 |
10. 最佳实践与使用建议
- 从简单开始:不要一开始就实现完整的、支持所有操作的规范模式。可以先从封装
Where条件开始,逐步添加Include、OrderBy和分页。 - 保持规范单一职责:一个规范类最好只代表一个明确的业务查询意图(如
ActiveProductsSpecification)。避免创建“万能”规范,通过组合来满足复杂需求。 - 考虑使用现有库:如果你需要生产级、功能全面的规范模式实现,可以考虑使用社区成熟库,如Ardalis.Specification。它提供了强大的组合、排序、分页、查询结果投影(
Select)等功能,并持续维护。 - 为规范编写单元测试:这是规范模式最大的优势之一。你可以轻松测试一个规范是否生成了正确的表达式树,而无需涉及数据库。
[Fact] public void ActiveProductsSpecification_Criteria_IsCorrect() { var spec = new ActiveProductsSpecification(); var product = new Product { IsActive = true }; var func = spec.Criteria?.Compile(); // 将表达式树编译为委托 Assert.NotNull(func); Assert.True(func(product)); // 应返回 true product.IsActive = false; Assert.False(func(product)); // 应返回 false } - 与 CQRS 结合:在采用命令查询职责分离架构的项目中,规范模式是实现复杂查询端的理想工具。每个查询(Query)可以对应一个或一组规范。
- 性能剖析:在开发过程中,始终关注 EF Core 生成的 SQL。规范模式只是组织代码,不会自动生成最优 SQL。复杂的
Include或子查询仍需人工审视。 - 明确边界:规范模式主要用于查询。对于创建、更新、删除等命令操作,通常不需要使用规范。
规范模式是清理 EF Core 查询代码库的一把利器。它通过将分散的、难以维护的查询逻辑封装成可组合、可测试的对象,显著提升了代码的清晰度和健壮性。虽然初始实现需要一些表达式树的知识,但其带来的长期维护收益是巨大的。对于任何面临复杂动态查询的 .NET 项目,投入时间引入规范模式都是一项值得的投资。建议从本文的基础实现开始,在项目中实践一两个核心查询,亲身体验其带来的结构优化,再逐步推广到整个数据访问层。
