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

.NET通用仓储实现动态分页、筛选等功能的方案及代码修改咨询

.NET通用仓储实现动态查询需求的方案与代码改造

问题背景

我正尝试在.NET中创建一款通用仓储(Generic Repository),使其支持动态分页、筛选、排序、列选择以及关联实体包含(includes)功能,目标是让该仓储具备足够灵活性以适配不同业务场景,无需为每种场景手动编写查询语句。请问.NET生态中有可实现该需求的NuGet包或设计模式吗?还是应采用Dynamic LINQ、Specification Pattern这类自定义方案?恳请提供相关最佳实践或示例参考!以下是我当前的实现代码,请问该如何修改以满足需求?

public abstract class GenericRepository<T> : IGenericRepository<T> where T : class
{
    private readonly EqpDbContext _dbContext;
    private readonly DbSet<T> _dbSet;

    public GenericRepository(EqpDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = _dbContext.Set<T>();
    }

    public virtual async Task<List<T>> GetAllAsync(bool includeDeleted = false, CancellationToken cancellationToken = default)
    {
        var query = _dbSet.AsNoTracking();

        if (PropertyExists("IsDeleted") && !includeDeleted)
        {
            query = query.Where(e => EF.Property<bool>(e, "IsDeleted") == false);
        }

        return await query.ToListAsync(cancellationToken);
    }

    public virtual async Task<T?> GetByIdAsync(int id, bool includeDeleted = false, CancellationToken cancellationToken = default)
    {
        var query = _dbSet.AsQueryable().AsNoTracking();

        if (PropertyExists("IsDeleted") && !includeDeleted)
        {
            query = query.Where(e => EF.Property<bool>(e, "IsDeleted") == false);
        }

        return await query.FirstOrDefaultAsync(e => EF.Property<int>(e, "Id") == id, cancellationToken: cancellationToken);
    }
}

一、可用的NuGet包选项

  • Entity Framework Plus:提供批量操作、查询缓存能力,同时简化动态筛选、关联实体包含的实现,内置扩展方法可快速构建复杂查询
  • AutoMapper.Extensions.ExpressionMapping:配合AutoMapper实现动态列选择,将DTO映射逻辑转换为EF查询的投影表达式,减少手动编写投影的工作量
  • DynamicLINQ.Core:直接支持字符串形式的Where、OrderBy等查询操作,快速实现完全动态的筛选和排序逻辑

二、推荐的设计模式方案

1. Specification模式

适用于业务查询规则相对固定的场景,每个查询逻辑封装为独立的Specification类,仓储层接收Specification实例来构建查询。优点是类型安全,便于单元测试和规则复用,避免字符串解析的潜在错误。

2. Dynamic LINQ方案

适用于需要完全动态的场景(比如前端自定义查询界面,用户输入任意筛选条件),直接通过字符串解析生成LINQ表达式树,灵活性极高,无需提前定义查询规则。注意:DynamicLINQ会将字符串转换为表达式树而非直接拼接SQL,不存在传统SQL注入风险。

三、现有代码的改造示例

1. 定义查询参数类

先创建一个统一的查询参数类,接收分页、筛选、排序、关联包含、列选择的参数:

public class QueryParameters
{
    public int PageIndex { get; set; } = 1;
    public int PageSize { get; set; } = 10;
    public string? SortBy { get; set; }
    public bool IsAscending { get; set; } = true;
    public string? Filter { get; set; } // DynamicLINQ格式的筛选条件,例如:"Name.Contains('test') && Age > 18"
    public List<string>? Includes { get; set; } // 关联实体路径,例如:["Orders", "Orders.Items"]
    public List<string>? SelectColumns { get; set; } // 要选择的列名,例如:["Id", "Name", "CreateTime"]
}

2. 改造GenericRepository

添加支持动态查询的核心方法,并整合所有需求功能:

using System.Linq.Dynamic.Core;
using System.Reflection;

public abstract class GenericRepository<T> : IGenericRepository<T> where T : class
{
    private readonly EqpDbContext _dbContext;
    private readonly DbSet<T> _dbSet;

    public GenericRepository(EqpDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = _dbContext.Set<T>();
    }

    // 新增:支持动态查询的核心方法
    public virtual async Task<(List<object> Items, int TotalCount)> GetDynamicAsync(QueryParameters parameters, bool includeDeleted = false, CancellationToken cancellationToken = default)
    {
        var query = _dbSet.AsNoTracking().AsQueryable();

        // 1. 软删除过滤
        if (PropertyExists("IsDeleted") && !includeDeleted)
        {
            query = query.Where(e => EF.Property<bool>(e, "IsDeleted") == false);
        }

        // 2. 关联实体包含
        if (parameters.Includes != null && parameters.Includes.Any())
        {
            foreach (var includePath in parameters.Includes)
            {
                query = query.Include(includePath);
            }
        }

        // 3. 动态筛选
        if (!string.IsNullOrWhiteSpace(parameters.Filter))
        {
            query = query.Where(parameters.Filter);
        }

        // 4. 统计总数量(分页前执行)
        var totalCount = await query.CountAsync(cancellationToken);

        // 5. 动态排序
        if (!string.IsNullOrWhiteSpace(parameters.SortBy))
        {
            var sortExpression = parameters.IsAscending 
                ? parameters.SortBy 
                : $"{parameters.SortBy} descending";
            query = query.OrderBy(sortExpression);
        }

        // 6. 分页
        query = query.Skip((parameters.PageIndex - 1) * parameters.PageSize)
                     .Take(parameters.PageSize);

        // 7. 动态列选择(投影到匿名类型)
        if (parameters.SelectColumns != null && parameters.SelectColumns.Any())
        {
            var selectExpression = string.Join(", ", parameters.SelectColumns);
            query = query.Select($"new({selectExpression})");
        }

        // 执行查询
        var items = await query.ToListAsync(cancellationToken);
        return (items, totalCount);
    }

    // 保留原有方法(可按需改造)
    public virtual async Task<List<T>> GetAllAsync(bool includeDeleted = false, CancellationToken cancellationToken = default)
    {
        var query = _dbSet.AsNoTracking();

        if (PropertyExists("IsDeleted") && !includeDeleted)
        {
            query = query.Where(e => EF.Property<bool>(e, "IsDeleted") == false);
        }

        return await query.ToListAsync(cancellationToken);
    }

    public virtual async Task<T?> GetByIdAsync(int id, bool includeDeleted = false, CancellationToken cancellationToken = default)
    {
        var query = _dbSet.AsQueryable().AsNoTracking();

        if (PropertyExists("IsDeleted") && !includeDeleted)
        {
            query = query.Where(e => EF.Property<bool>(e, "IsDeleted") == false);
        }

        return await query.FirstOrDefaultAsync(e => EF.Property<int>(e, "Id") == id, cancellationToken: cancellationToken);
    }

    // 辅助方法:检查实体是否包含指定属性
    private bool PropertyExists(string propertyName)
    {
        return typeof(T).GetProperty(
            propertyName, 
            BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null;
    }
}

四、最佳实践提示

  • 优先选择Specification模式:如果业务规则稳定,强类型的Specification更易维护和测试,避免字符串解析的潜在错误
  • Dynamic LINQ适合动态场景:比如前端自定义查询界面,需要用户输入任意筛选条件时使用,注意验证输入格式避免解析错误
  • 列选择与投影:尽量使用投影减少数据传输,避免返回不必要的字段,提升查询性能
  • 关联包含注意性能:避免一次性包含过多层级的关联实体,可按需分批次查询或使用延迟加载(延迟加载需注意N+1查询问题,可通过Include或ThenInclude提前加载关联数据)

内容的提问来源于stack exchange,提问作者Lan Lin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:02:08