当前位置: 首页 > news >正文

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

 /// <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; }}

在这里插入图片描述


文章转载自:
http://adsorbability.hyyxsc.cn
http://chromatopsia.hyyxsc.cn
http://chittamwood.hyyxsc.cn
http://agalite.hyyxsc.cn
http://albinism.hyyxsc.cn
http://bioflick.hyyxsc.cn
http://carzey.hyyxsc.cn
http://camphorate.hyyxsc.cn
http://abas.hyyxsc.cn
http://beggarly.hyyxsc.cn
http://ahimsa.hyyxsc.cn
http://chace.hyyxsc.cn
http://blackmarket.hyyxsc.cn
http://belfry.hyyxsc.cn
http://biotin.hyyxsc.cn
http://belock.hyyxsc.cn
http://cacophony.hyyxsc.cn
http://actinotheraphy.hyyxsc.cn
http://astarte.hyyxsc.cn
http://break.hyyxsc.cn
http://bilgy.hyyxsc.cn
http://bedtiime.hyyxsc.cn
http://broadcatching.hyyxsc.cn
http://aphoristic.hyyxsc.cn
http://admire.hyyxsc.cn
http://celery.hyyxsc.cn
http://bluesman.hyyxsc.cn
http://bleeder.hyyxsc.cn
http://arthrosporous.hyyxsc.cn
http://acceleration.hyyxsc.cn
http://www.dtcms.com/a/280865.html

相关文章:

  • C语言基础第6天:分支循环
  • Ubuntu24 辅助系统-屏幕键盘的back按键在网页文本框删除不正常的问题解决方法
  • CentOS7 Docker安装MySQL全过程,包括配置远程连接账户
  • fastApi连接数据库
  • 如何正确分配及设置香港站群服务器IP?
  • 深入解析 Java AQS (AbstractQueuedSynchronizer) 的实现原理
  • LeetCode 3136.有效单词:遍历模拟
  • [实战] 基8 FFT/IFFT算法原理与实现(完整C代码)
  • 【每天一个知识点】多模态信息(Multimodal Information)
  • 【知识扫盲】tokenizer.json中的vocab和merges是什么?
  • 【机器学习】第二章 Python入门
  • 【Unity】MiniGame编辑器小游戏(十四)基础支持模块(游戏窗口、游戏对象、物理系统、动画系统、射线检测)
  • 数学中的教学思想
  • MySQL 8.0 OCP 1Z0-908 题目解析(24)
  • P3842 [TJOI2007] 线段
  • Sharding-JDBC 分布式事务实战指南:XA/Seata 方案解析
  • sqli-labs靶场通关笔记:第18-19关 HTTP头部注入
  • 【C++】初识C++(1)
  • 课题学习笔记1——文本问答与信息抽取关键技术研究论文阅读(用于无结构化文本问答的文本生成技术)
  • Java 大视界 -- Java 大数据机器学习模型在金融风险传染路径分析与防控策略制定中的应用(347)
  • QT——QList的详细讲解
  • Redis的下载安装+基础操作+redis客户端的安装
  • 使用 1Panel PHP 运行环境部署 WordPress
  • 辨析git reset三种模式以及和git revert的区别:回退到指定版本和撤销指定版本的操作
  • 零样本轴承故障诊断SC - GAN模型
  • 【PCIe 总线及设备入门学习专栏 5.1.2 -- PCIe EP core_rst_n 与 app_rst_n】
  • React-router
  • 未来大模型在中小型企业如何实现普及
  • PG备份一(逻辑备份)
  • Kafka——生产者消息分区机制原理剖析