.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
相关产品推荐
相关产品推荐

