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

为泛型仓储查询添加多条件Where子句的实现求助

动态构建泛型仓储的查询条件及优化实现

完全可以基于非空输入值,将字符串或列表值作为Where条件动态加入泛型查询,核心是通过表达式树(Expression Tree)动态构建过滤条件,再与原有仓储方法结合。以下是具体实现方案:


1. 构建动态表达式辅助类

先实现一个通用的表达式工具类,用来生成单个过滤条件并合并多个条件:

public static class ExpressionBuilder
{
    // 生成「属性等于指定值」的表达式
    public static Expression<Func<T, bool>> Equal<T>(string propertyName, object value)
    {
        var parameter = Expression.Parameter(typeof(T), "entity");
        var property = Expression.Property(parameter, propertyName);
        var constant = Expression.Constant(value);
        var equalExpr = Expression.Equal(property, constant);
        
        return Expression.Lambda<Func<T, bool>>(equalExpr, parameter);
    }

    // 生成「属性值在指定列表中」的Contains表达式
    public static Expression<Func<T, bool>> Contains<T, TValue>(string propertyName, IEnumerable<TValue> values)
    {
        var parameter = Expression.Parameter(typeof(T), "entity");
        var property = Expression.Property(parameter, propertyName);
        var valuesConstant = Expression.Constant(values);
        
        var containsMethod = typeof(Enumerable)
            .GetMethod("Contains", new[] { typeof(IEnumerable<TValue>), typeof(TValue) });
        var containsExpr = Expression.Call(containsMethod, valuesConstant, property);
        
        return Expression.Lambda<Func<T, bool>>(containsExpr, parameter);
    }

    // 生成字符串模糊匹配(Contains)的表达式
    public static Expression<Func<T, bool>> StringContains<T>(string propertyName, string value)
    {
        var parameter = Expression.Parameter(typeof(T), "entity");
        var property = Expression.Property(parameter, propertyName);
        var constant = Expression.Constant(value, typeof(string));
        
        var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var containsExpr = Expression.Call(property, containsMethod, constant);
        
        return Expression.Lambda<Func<T, bool>>(containsExpr, parameter);
    }

    // 合并多个条件为And逻辑
    public static Expression<Func<T, bool>> CombineAnd<T>(params Expression<Func<T, bool>>[] expressions)
    {
        if (expressions == null || expressions.Length == 0)
            return null;

        var combinedExpr = expressions[0];
        for (int i = 1; i < expressions.Length; i++)
        {
            var nextExpr = expressions[i];
            combinedExpr = Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(combinedExpr.Body, Expression.Invoke(nextExpr, combinedExpr.Parameters[0])),
                combinedExpr.Parameters[0]);
        }
        return combinedExpr;
    }
}

2. 优化原有泛型仓储方法

针对你原有的allRecord方法,做如下优化(符合C#编码规范、依赖注入原则):

public class GenericRepository<T> where T : class
{
    private readonly Context _dbContext;

    // 通过构造函数注入DbContext,避免每次创建新实例,提升性能和可测试性
    public GenericRepository(Context dbContext)
    {
        _dbContext = dbContext;
    }

    public List<T> AllRecords(Expression<Func<T, bool>> filter = null,
                              int skip = 0,
                              int take = int.MaxValue,
                              Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
                              IList<string> includes = null)
    {
        IQueryable<T> query = _dbContext.Set<T>();

        // 应用过滤条件
        if (filter != null)
        {
            query = query.Where(filter);
        }

        // 批量处理Include关联查询
        if (includes != null)
        {
            query = includes.Aggregate(query, (currentQuery, includePath) => currentQuery.Include(includePath));
        }

        // 应用排序
        if (orderBy != null)
        {
            query = orderBy(query);
        }

        // 应用分页(Skip(0)不影响结果,简化逻辑)
        query = query.Skip(skip).Take(take);

        return query.ToList();
    }
}

3. 调用示例:基于非空输入构建查询

假设你有一个包含20个输入字段的查询DTO(如SearchParams),只需判断每个字段是否非空,生成对应条件后合并,再传入仓储方法:

// 示例查询参数DTO
public class SearchParams
{
    public string ProductName { get; set; }
    public List<int> CategoryIds { get; set; }
    public decimal? MinPrice { get; set; }
    // 剩余17个输入字段...
}

// 调用仓储的代码
var searchParams = new SearchParams
{
    ProductName = "手机",
    CategoryIds = new List<int> { 1, 3 },
    MinPrice = 1000M
};

var conditions = new List<Expression<Func<Product, bool>>>();

// 非空判断后添加对应条件
if (!string.IsNullOrEmpty(searchParams.ProductName))
{
    conditions.Add(ExpressionBuilder.StringContains<Product>(nameof(Product.Name), searchParams.ProductName));
}

if (searchParams.CategoryIds != null && searchParams.CategoryIds.Any())
{
    conditions.Add(ExpressionBuilder.Contains<Product, int>(nameof(Product.CategoryId), searchParams.CategoryIds));
}

if (searchParams.MinPrice.HasValue)
{
    // 构建「价格大于等于最小值」的表达式
    var parameter = Expression.Parameter(typeof(Product), "p");
    var priceProp = Expression.Property(parameter, nameof(Product.Price));
    var minPriceConst = Expression.Constant(searchParams.MinPrice.Value);
    var greaterExpr = Expression.GreaterThanOrEqual(priceProp, minPriceConst);
    conditions.Add(Expression.Lambda<Func<Product, bool>>(greaterExpr, parameter));
}

// 合并所有条件
var filter = ExpressionBuilder.CombineAnd<Product>(conditions.ToArray());

// 注入仓储实例后调用方法
var productRepo = new GenericRepository<Product>(new Context());
var result = productRepo.AllRecords(filter, skip: 0, take: 20);

注意事项

  • 确保传入的propertyName是实体类T中存在的属性,否则会抛出运行时异常,可添加typeof(T).GetProperty(propertyName)的验证逻辑。
  • 可根据需求扩展ExpressionBuilder,添加大于、小于、不等于等更多比较逻辑的方法。
  • 若使用依赖注入容器(如Autofac、Microsoft DI),建议将GenericRepository<T>和Context注册到容器中,避免手动实例化。

内容的提问来源于stack exchange,提问作者Halil İbrahim AYHAN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 13:12:02