C# 构建动态查询表达式(含查询、排序、分页)

发布于:2025-07-16 ⋅ 阅读:(14) ⋅ 点赞:(0)
 /// <summary>
 /// 动态查询创建者
 /// </summary>
 public static class DynamicQueryBuilder
 {
     /// <summary>
     /// 构建查询
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="source"></param>
     /// <param name="parameters"></param>
     /// <param name="allowedFilterFields"></param>
     /// <returns></returns>
     public static IQueryable<T> BuildQuery<T>(
         this IQueryable<T> source,
         DynamicQueryParams parameters,
         List<string>? allowedFilterFields = null)
     {
         var query = source;

         // 动态过滤
         query = ApplyFilters(query, JsonConvert.DeserializeObject<Dictionary<string, string>>(parameters.Filters ?? ""), allowedFilterFields);

         // 动态排序
         query = ApplySorting(query, parameters.SortField, parameters.SortDirection);

         // 分页
         return ApplyPagination(query, parameters.PageIndex, parameters.PageSize);
     }

     /// <summary>
     /// 应用过滤器
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="source"></param>
     /// <param name="filters"></param>
     /// <param name="allowedFields"></param>
     /// <returns></returns>
     private static IQueryable<T> ApplyFilters<T>(
         IQueryable<T> source,
         Dictionary<string, string> filters,
         List<string>? allowedFields)
     {
         if (filters == null || !filters.Any())
             return source;

         var parameter = Expression.Parameter(typeof(T), "x");
         Expression? combinedExpression = null;

         foreach (var filter in filters)
         {
             var fieldName = filter.Key;
             var searchValue = filter.Value;

             // 安全验证:检查字段是否允许查询
             if (allowedFields != null && !allowedFields.Contains(fieldName))
                 continue;

             // 获取属性表达式
             var property = GetNestedPropertyExpression(parameter, fieldName);
             if (property == null) continue;

             // 解析操作符和值
             var (operatorType, cleanValue) = ParseOperator(searchValue);

             // 构建表达式
             var condition = BuildConditionExpression(
                 property,
                 cleanValue,
                 operatorType,
                 parameter);

             if (condition == null) continue;

             combinedExpression = combinedExpression == null
                 ? condition
                 : Expression.AndAlso(combinedExpression, condition);
         }

         return combinedExpression == null
             ? source
             : source.Where(Expression.Lambda<Func<T, bool>>(combinedExpression, parameter));
     }

     /// <summary>
     /// 解析操作器(高版本可以用这个)
     /// </summary>
     /// <param name="value"></param>
     /// <returns></returns>
     //private static (string Operator, string Value) ParseOperator(string value)
     //{
     //    if (value.StartsWith(">=")) return (">=", value[2..]);
     //    if (value.StartsWith("<=")) return ("<=", value[2..]);
     //    if (value.StartsWith(">")) return (">", value[1..]);
     //    if (value.StartsWith("<")) return ("<", value[1..]);
     //    if (value.StartsWith("=")) return ("=", value[2..]);
     //    if (value.StartsWith("!=")) return ("!=", value[2..]);
     //    return ("", value);
     //}

     /// <summary>
     /// 解析操作器
     /// </summary>
     /// <param name="value"></param>
     /// <returns></returns>
     private static (string Operator, string Value) ParseOperator(string value)
     {
         // 空值处理
         if (string.IsNullOrWhiteSpace(value))
             return (string.Empty, value);

         // 支持语义化字符串操作符(优先检查)
         if (value.StartsWith("contains:") && value.Length > 9)
         {
             return ("contains", value.Substring(9));
         }

         if (value.StartsWith("startswith:") && value.Length > 11)
         {
             return ("startswith", value.Substring(11));
         }

         if (value.StartsWith("endswith:") && value.Length > 9)
         {
             return ("endswith", value.Substring(9));
         }

         // 比较操作符(按长度降序检查)
         if (value.StartsWith(">=") && value.Length > 2)
         {
             return (">=", value.Substring(2).Trim());
         }

         if (value.StartsWith("<=") && value.Length > 2)
         {
             return ("<=", value.Substring(2).Trim());
         }

         if (value.StartsWith("!=") && value.Length > 2)
         {
             return ("!=", value.Substring(2).Trim());
         }

         if (value.StartsWith(">") && value.Length > 1)
         {
             return (">", value.Substring(1).Trim());
         }

         if (value.StartsWith("<") && value.Length > 1)
         {
             return ("<", value.Substring(1).Trim());
         }

         if (value.StartsWith("=") && value.Length > 1)
         {
             return ("=", value.Substring(1).Trim());
         }

         // 默认返回原始值
         return (string.Empty, value.Trim());
     }

     /// <summary>
     /// 构建条件表达式
     /// </summary>
     /// <param name="property"></param>
     /// <param name="stringValue"></param>
     /// <param name="operatorType"></param>
     /// <param name="parameter"></param>
     /// <returns></returns>
     private static Expression? BuildConditionExpression(
         Expression property,
         string stringValue,
         string operatorType,
         ParameterExpression parameter)
     {
         try
         {
             // 处理可空类型
             Type targetType = property.Type;
             bool isNullable = false;

             if (Nullable.GetUnderlyingType(targetType) is Type underlyingType)
             {
                 targetType = underlyingType;
                 isNullable = true;
             }

             // 特殊类型处理
             if (targetType == typeof(Guid))
             {
                 return HandleGuidCondition(property, stringValue, operatorType, isNullable, parameter);
             }
             else if (targetType.IsEnum)
             {
                 return HandleEnumCondition(property, stringValue, operatorType, targetType, isNullable);
             }

             // 转换值到目标类型
             object? value = Convert.ChangeType(stringValue, targetType);

             // 处理空值检查
             Expression nullSafeProperty = property;
             if (isNullable || !property.Type.IsValueType)
             {
                 var nullCheck = Expression.NotEqual(
                     property,
                     Expression.Constant(null, property.Type));

                 // 对于可空类型,使用基础类型进行比较
                 Expression conditionExpression = BuildComparisonExpression(
                     isNullable ? Expression.Property(property, "Value") : property,
                     value,
                     operatorType,
                     targetType);

                 return Expression.AndAlso(nullCheck, conditionExpression);
             }

             return BuildComparisonExpression(property, value, operatorType, targetType);
         }
         catch
         {
             // 类型转换失败时尝试字符串匹配
             if (property.Type == typeof(string))
             {
                 return BuildStringCondition(property, stringValue, operatorType);
             }
             return null;
         }
     }

     /// <summary>
     /// 构建比较表达式
     /// </summary>
     /// <param name="property"></param>
     /// <param name="value"></param>
     /// <param name="operatorType"></param>
     /// <param name="targetType"></param>
     /// <returns></returns>
     private static Expression BuildComparisonExpression(
         Expression property,
         object value,
         string operatorType,
         Type targetType)
     {
         var valueExpression = Expression.Constant(value, targetType);

         return operatorType switch
         {
             "=" => Expression.Equal(property, valueExpression),
             "!=" => Expression.NotEqual(property, valueExpression),
             ">" => Expression.GreaterThan(property, valueExpression),
             "<" => Expression.LessThan(property, valueExpression),
             ">=" => Expression.GreaterThanOrEqual(property, valueExpression),
             "<=" => Expression.LessThanOrEqual(property, valueExpression),
             _ => null
         };
     }

     /// <summary>
     /// 构建字符串条件
     /// </summary>
     /// <param name="property"></param>
     /// <param name="stringValue"></param>
     /// <param name="operatorType"></param>
     /// <returns></returns>
     private static Expression? BuildStringCondition(
         Expression property,
         string stringValue,
         string operatorType)
     {
         switch (operatorType)
         {
             case "=":
                 return Expression.Equal(
                     property,
                     Expression.Constant(stringValue));
             case "!=":
                 return Expression.NotEqual(
                     property,
                     Expression.Constant(stringValue));
             case "contains":
             case "":
                 var containsMethod = typeof(string).GetMethod("Contains", [typeof(string)]);
                 return Expression.Call(property, containsMethod!, Expression.Constant(stringValue));
             case "startswith":
                 var startsWithMethod = typeof(string).GetMethod("StartsWith", [typeof(string)]);
                 return Expression.Call(property, startsWithMethod!, Expression.Constant(stringValue));
             case "endswith":
                 var endsWithMethod = typeof(string).GetMethod("EndsWith", [typeof(string)]);
                 return Expression.Call(property, endsWithMethod!, Expression.Constant(stringValue));
             default:
                 return null;
         }
     }

     /// <summary>
     /// 处理枚举条件
     /// </summary>
     /// <param name="property"></param>
     /// <param name="stringValue"></param>
     /// <param name="operatorType"></param>
     /// <param name="enumType"></param>
     /// <param name="isNullable"></param>
     /// <returns></returns>
     private static Expression? HandleEnumCondition(
         Expression property,
         string stringValue,
         string operatorType,
         Type enumType,
         bool isNullable)
     {
         try
         {
             object enumValue = Enum.Parse(enumType, stringValue, true);
             var valueExpression = Expression.Constant(enumValue, enumType);

             // 如果是可空类型,需要访问.Value属性
             Expression comparisonProperty = isNullable ?
                 Expression.Property(property, "Value") :
                 property;

             return operatorType switch
             {
                 "=" => Expression.Equal(comparisonProperty, valueExpression),
                 "!=" => Expression.NotEqual(comparisonProperty, valueExpression),
                 _ => null
             };
         }
         catch
         {
             return null;
         }
     }

     /// <summary>
     /// 处理Guid条件
     /// </summary>
     /// <param name="property"></param>
     /// <param name="stringValue"></param>
     /// <param name="operatorType"></param>
     /// <param name="isNullable"></param>
     /// <param name="parameter"></param>
     /// <returns></returns>
     private static Expression? HandleGuidCondition(
         Expression property,
         string stringValue,
         string operatorType,
         bool isNullable,
         ParameterExpression parameter)
     {
         try
         {
             Guid guidValue = Guid.Parse(stringValue);
             var valueExpression = Expression.Constant(guidValue, typeof(Guid));

             // 如果是可空类型,需要访问.Value属性
             Expression comparisonProperty = isNullable ?
                 Expression.Property(property, "Value") :
                 property;

             return operatorType switch
             {
                 "=" => Expression.Equal(comparisonProperty, valueExpression),
                 "!=" => Expression.NotEqual(comparisonProperty, valueExpression),
                 _ => null
             };
         }
         catch
         {
             return null;
         }
     }

     /// <summary>
     /// 获取嵌套属性表达式
     /// </summary>
     /// <param name="parameter"></param>
     /// <param name="path"></param>
     /// <returns></returns>
     private static Expression? GetNestedPropertyExpression(Expression parameter, string path)
     {
         try
         {
             return path.Split('.')
                 .Aggregate(parameter, (current, property) =>
                     Expression.Property(current, property));
         }
         catch
         {
             return null;
         }
     }

     /// <summary>
     /// 应用排序
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="source"></param>
     /// <param name="sortField"></param>
     /// <param name="sortDirection"></param>
     /// <returns></returns>
     private static IQueryable<T> ApplySorting<T>(
         IQueryable<T> source,
         string? sortField,
         string? sortDirection)
     {
         if (string.IsNullOrWhiteSpace(sortField))
             return source;

         var parameter = Expression.Parameter(typeof(T), "x");
         var property = GetNestedPropertyExpression(parameter, sortField);
         if (property == null) return source;

         var lambda = Expression.Lambda(property, parameter);
         var methodName = string.Equals(sortDirection, "desc", StringComparison.OrdinalIgnoreCase)
             ? "OrderByDescending"
             : "OrderBy";

         var result = Expression.Call(
             typeof(Queryable),
             methodName,
             [typeof(T), property.Type],
             source.Expression,
             Expression.Quote(lambda));

         return source.Provider.CreateQuery<T>(result);
     }

     /// <summary>
     /// 应用分页
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="source"></param>
     /// <param name="pageIndex"></param>
     /// <param name="pageSize"></param>
     /// <returns></returns>
     private static IQueryable<T> ApplyPagination<T>(
         IQueryable<T> source,
         int pageIndex,
         int pageSize)
     {
         if (pageIndex >= 1 && pageSize >= 1)
             return source.Skip((pageIndex - 1) * pageSize).Take(pageSize);
         else return source;
     }
 }

 public class DynamicQueryParams
 {
     // 分页参数
     public int PageIndex { get; set; } = 1;
     public int PageSize { get; set; } = 10;

     // 排序参数
     public string? SortField { get; set; }
     public string? SortDirection { get; set; } = "asc"; // asc/desc

     // 动态过滤字典 (Key: 字段名, Value: 搜索值)
     //public Dictionary<string, string> Filters { get; set; } = new ();
     public string? Filters { get; set; }
 }

测试

 [Route("api/[controller]")]
 [ApiController]
 public class ProductController : ControllerBase
 {
     [HttpGet]
     public IActionResult GetProducts([FromQuery] DynamicQueryParams parameters)
     {
         var products = LoadProducts();
         var query = products
             .AsQueryable()
             .BuildQuery(parameters, null);

         // 返回结果(包含分页元数据)
         var totalCount = query.Count();
         var results = query.ToList();

         return Ok(new
         {
             Data = results,
             PageIndex = parameters.PageIndex,
             PageSize = parameters.PageSize,
             TotalCount = totalCount,
             TotalPages = (int)Math.Ceiling(totalCount / (double)parameters.PageSize)
         });
     }

     private List<Product> LoadProducts()
     {
         return new List<Product>()
         {
             new Product{Id = 1, Code = "C001", Name = "产品001", Price = 10, CreateTime = Convert.ToDateTime("2025-07-11 08:00:00") },
             new Product{Id = 2, Code = "C012", Name = "产品012", Price = 20, CreateTime = Convert.ToDateTime("2025-07-12 08:00:00") },
             new Product{Id = 3, Code = "C003", Name = "产品003", Price = 10, CreateTime = Convert.ToDateTime("2025-07-11 10:00:00") },
             new Product{Id = 4, Code = "C004", Name = "产品004", Price = 30, CreateTime = Convert.ToDateTime("2025-07-11 16:00:00") },
             new Product{Id = 5, Code = "C005", Name = "产品005", Price = 25, CreateTime = Convert.ToDateTime("2025-07-13 08:00:00") },
             new Product{Id = 6, Code = "C006", Name = "产品006", Price = 10, CreateTime = Convert.ToDateTime("2025-07-15 08:00:00") },
         };
     }
 }

 public class Product
 {
     public int Id { get; set; }
     public string Code { get; set; }
     public string Name { get; set; }
     public int Price { get; set; }
     public DateTime CreateTime { get; set; }
 }

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到