如何使用Ardalis Repository Specification实现不同实体的Union All?
在Ardalis CleanArchitecture中跨实体实现Union All分页搜索
1. 定义统一返回DTO
首先创建一个DTO来统一两个实体的返回结构,包含所需字段和区分标识:
public class ProductSearchResult { // 若两个实体ID类型不同,可改为string并在映射时转字符串 public int Id { get; set; } public string Name { get; set; } public bool IsKit { get; set; } }
2. 实现跨实体查询逻辑
Ardalis Specification主要针对单个实体,跨实体Union All更适合结合EF Core的查询能力实现,以下提供两种实现方式:
方式一:直接在Repository中编写查询
在CleanArchitecture的Infrastructure层实现专门的搜索Repository(或扩展现有Repository):
// Application层定义接口 public interface ISearchRepository { Task<PaginatedList<ProductSearchResult>> SearchProductsAsync( string searchTerm, int pageNumber, int pageSize, CancellationToken cancellationToken = default); } // Infrastructure层实现 public class SearchRepository : ISearchRepository { private readonly AppDbContext _dbContext; public SearchRepository(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<PaginatedList<ProductSearchResult>> SearchProductsAsync( string searchTerm, int pageNumber, int pageSize, CancellationToken cancellationToken = default) { // 构建Product查询并映射到DTO var productQuery = _dbContext.Products .Where(p => string.IsNullOrEmpty(searchTerm) || p.Name.Contains(searchTerm)) .Select(p => new ProductSearchResult { Id = p.Id, Name = p.Name, IsKit = false }); // 构建ProductKit查询并映射到DTO var productKitQuery = _dbContext.ProductKits .Where(pk => string.IsNullOrEmpty(searchTerm) || pk.Name.Contains(searchTerm)) .Select(pk => new ProductSearchResult { Id = pk.Id, Name = pk.Name, IsKit = true }); // 使用Concat实现SQL的Union All(Union会去重,Concat保留所有结果) var combinedQuery = productQuery.Concat(productKitQuery); // 应用排序和分页,使用Ardalis自带的PaginatedList return await PaginatedList<ProductSearchResult>.CreateAsync( combinedQuery.OrderBy(result => result.Name), pageNumber, pageSize, cancellationToken); } }
方式二:结合Specification复用查询逻辑
若想复用现有Specification的查询规则,可为每个实体创建映射到DTO的Specification:
// Product的Specification public class ProductByNameSpecification : Specification<Product, ProductSearchResult> { public ProductByNameSpecification(string searchTerm) { Query.Where(p => string.IsNullOrEmpty(searchTerm) || p.Name.Contains(searchTerm)) .Select(p => new ProductSearchResult { Id = p.Id, Name = p.Name, IsKit = false }); } } // ProductKit的Specification public class ProductKitByNameSpecification : Specification<ProductKit, ProductSearchResult> { public ProductKitByNameSpecification(string searchTerm) { Query.Where(pk => string.IsNullOrEmpty(searchTerm) || pk.Name.Contains(searchTerm)) .Select(pk => new ProductSearchResult { Id = pk.Id, Name = pk.Name, IsKit = true }); } }
随后在Repository中获取两个Specification对应的IQueryable并拼接:
public async Task<PaginatedList<ProductSearchResult>> SearchProductsAsync( string searchTerm, int pageNumber, int pageSize, CancellationToken cancellationToken = default) { var productSpec = new ProductByNameSpecification(searchTerm); var productQuery = _productRepository.GetQueryable(productSpec); var productKitSpec = new ProductKitByNameSpecification(searchTerm); var productKitQuery = _productKitRepository.GetQueryable(productKitSpec); var combinedQuery = productQuery.Concat(productKitQuery); return await PaginatedList<ProductSearchResult>.CreateAsync( combinedQuery.OrderBy(result => result.Name), pageNumber, pageSize, cancellationToken); }
3. 在Application UseCase中调用
在Application层的查询用例中注入ISearchRepository,调用方法后将结果返回给Presentation层即可。
注意事项
- 若Product和ProductKit的ID类型不一致(如一个int、一个Guid),将DTO的Id改为
string,映射时用Id = p.Id.ToString()统一格式。 - 搜索条件可按需调整,比如使用
EF.Functions.Like实现模糊匹配,或添加区分大小写的配置。 - EF Core中
Concat对应SQL的UNION ALL,Union对应去重的UNION,可根据需求选择。
内容的提问来源于stack exchange,提问作者Elivelton Repolho
相关产品推荐
相关产品推荐

