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