You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 09:15:38