eShopOnWeb商品查询拆解:Specification模式动态过滤与分页完整指南
【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWeb
🛒 eShopOnWeb 是一个基于 ASP.NET Core 8.0 的开源电商参考应用,它的商品查询功能完整演示了Specification(规约)模式如何实现动态过滤与分页。本文带你从请求入口一路追踪到数据库条件拼接,看它是如何用不到 20 行代码写出"品牌+类型+页码"组合查询的——全程几乎不用写 SQL 拼接代码。
为什么需要 Specification 模式?
在传统写法里,"按品牌筛选 + 按类型筛选 + 分页"往往要写一个带一堆if判断的查询方法,参数组合一多,代码就成了面条:
- 每加一个筛选条件,就要改方法签名和内部逻辑
- 查询条件散落在各处,难以复用、难以单独测试
Specification 模式的思路是:把"查什么"封装成一个个可复用的对象。每个筛选规则是一个独立的小类,查询执行器负责把它翻译成 SQL。规则之间互不影响,随便组合。
eShopOnWeb 使用的是社区流行的Ardalis.Specification库,所有规约类集中在 Specifications 目录下。
查询全链路:从 URL 参数到 SQL
一次商品列表请求的流转路径非常清晰:
- 用户在首页选择品牌、类型并翻页,浏览器带上
pageId、BrandFilterApplied等查询参数 - 页面模型 Index.cshtml.cs 的
OnGet接收参数,转交给服务层 - 服务层 CatalogViewModelService.cs 构建 Specification 对象,调用仓储执行查询
- 仓储将 Specification 翻译成数据库查询条件并返回结果
拆解 4 个核心 Specification 类
商品相关的规约共有 4 个,都是"小而美":
1️⃣ 动态过滤:CatalogFilterSpecification
CatalogFilterSpecification.cs 是整个过滤的核心,全部逻辑就一行Where:
Query.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) && (!typeId.HasValue || i.CatalogTypeId == typeId));技巧在于!brandId.HasValue ||这个短路判断:参数没传时该条件自动失效,传了才生效。两个参数任意组合(都传、只传一个、都不传),一个类全部覆盖,这就是"动态过滤"的精髓。
2️⃣ 过滤 + 分页:CatalogFilterPaginatedSpecification
CatalogFilterPaginatedSpecification.cs 在相同过滤条件上追加了.Skip(skip).Take(take),负责只取当前页数据。它还处理了一个细节:当take为 0 时自动改为int.MaxValue,相当于"返回全部",避免分页参数异常时查不到数据。
3️⃣ 按 ID 批量查:CatalogItemsSpecification
CatalogItemsSpecification.cs 接收一组 ID,用于购物车结算等场景按 ID 批量取出商品,params int[] ids让调用侧可以传入任意数量。
4️⃣ 按名称查:CatalogItemNameSpecification
CatalogItemNameSpecification.cs 按商品名精确查找,管理端编辑商品时用来防止重名。
分页是怎么算出来的?
真正的分页"数学"发生在服务层 CatalogViewModelService.cs 的GetCatalogItems方法里:
- 取当前页数据:
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId)—— 页码 × 每页条数就是Skip的偏移量,每页条数取自 Constants.cs 中的ITEMS_PER_PAGE = 10 - 取总数:单独用一个纯过滤的
CatalogFilterSpecification执行CountAsync,只数数量不取实体,性能友好 - 算总页数:总数 ÷ 每页条数向上取整,连同当前页码一起装进 PaginationInfoViewModel.cs
注意这里"取数据"和"数总数"用了两个不同的 Specification——一个带分页、一个不带,各取所需。这正是规约对象可以随意裁剪、组合的威力。
前端分页条:条件自动跟随
分页链接由 _pagination.cshtml 渲染,它的巧妙之处在于:把当前请求的全部查询参数原样复制到新链接上,只替换pageId。也就是说,你正在按"品牌 A + 类型 B"筛选,翻页后筛选条件依然保留,用户不会莫名其妙看到全部商品。
总结:这套设计值得抄作业的地方
✅规则即对象:每个筛选条件是独立类,加新规则零侵入 ✅参数可空即可选:!xxx.HasValue ||让动态条件一行搞定 ✅取数与计数分离:分页查询和总数查询各司其职 ✅分层清晰:页面模型只管传参,服务层构建规约,仓储只管执行
所有规格源码都在 src/ApplicationCore/Specifications/,单元测试覆盖了大部分规约,见 tests/UnitTests/ApplicationCore/Specifications/。想在自己项目里落地同样的动态过滤与分页,照这个"一个条件一个类"的结构抄即可。
【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWeb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考