1.创建需要的对象,如下代码:
public class QueryCondition
{
public List<QueryObject> QueryObjects { get; set; }
public List<OrderByObject> OrderBys { get; set; }
public string LogicalOperator { get; set; }
}
public class QueryObject
{
public string PropertyName { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
}
public class OrderByObject
{
public string PropertyName { get; set; }
public bool Ascending { get; set; }
}
2.构建个QueryableExtension类,写个方法BuildMultiConditionQueryExpression
public static class QueryableExtensions
{
public static IQueryable<T> DynamicQuery<T>(this IQueryable<T> source, List<QueryObject> conditions, string logicalOperator, List<OrderByObject> orderBys)
{
var predicate = BuildQueryExpression<T>(conditions, logicalOperator);
var query = source.Where(predicate);
if (orderBys != null && orderBys.Any())
{
query = ApplyOrderBy(query, orderBys);
}
return query;
}
public static Expression<Func<T, bool>> BuildQueryExpression<T>(List<QueryObject> conditions, string logicalOperator)
{
var parameter = Expression.Parameter(typeof(T), "x");
var expressions = new List<Expression>();
foreach (var condition in conditions)
{
var property = typeof(T).GetProperty(condition.PropertyName);
if (property == null)
{
throw new ArgumentException($"Property '{condition.PropertyName}' not found on type '{typeof(T).Name}'.");
}
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
Expression comparisonExpression;
switch (condition.Operator.ToLower())
{
case "=":
case "==":
var constantValueEqual = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.Equal(propertyAccess, constantValueEqual);
break;
case "!=":
var constantValueNotEqual = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.NotEqual(propertyAccess, constantValueNotEqual);
break;
case ">":
var constantValueGreaterThan = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.GreaterThan(propertyAccess, constantValueGreaterThan);
break;
case ">=":
var constantValueGreaterThanOrEqual = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.GreaterThanOrEqual(propertyAccess, constantValueGreaterThanOrEqual);
break;
case "<":
var constantValueLessThan = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.LessThan(propertyAccess, constantValueLessThan);
break;
case "<=":
var constantValueLessThanOrEqual = Expression.Constant(condition.Value, property.PropertyType);
comparisonExpression = Expression.LessThanOrEqual(propertyAccess, constantValueLessThanOrEqual);
break;
case "contains":
var methodInfo_contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var constantValueContains = Expression.Constant(condition.Value.ToString(), typeof(string));
comparisonExpression = Expression.Call(propertyAccess, methodInfo_contains, constantValueContains);
break;
case "startswith":
var methodInfo_startswith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var constantValueStartsWith = Expression.Constant(condition.Value.ToString(), typeof(string));
comparisonExpression = Expression.Call(propertyAccess, methodInfo_startswith, constantValueStartsWith);
break;
case "endswith":
var methodInfo_endswith = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var constantValueEndsWith = Expression.Constant(condition.Value.ToString(), typeof(string));
comparisonExpression = Expression.Call(propertyAccess, methodInfo_endswith, constantValueEndsWith);
break;
default:
throw new ArgumentException($"Unsupported comparison operator: {condition.Operator}");
}
expressions.Add(comparisonExpression);
}
Expression combinedExpression;
if (logicalOperator.ToLower() == "and")
{
combinedExpression = expressions.Aggregate((acc, expr) => Expression.AndAlso(acc, expr));
}
else if (logicalOperator.ToLower() == "or")
{
combinedExpression = expressions.Aggregate((acc, expr) => Expression.OrElse(acc, expr));
}
else
{
throw new ArgumentException($"Unsupported logical operator: {logicalOperator}");
}
return Expression.Lambda<Func<T, bool>>(combinedExpression, parameter);
}
public static IOrderedQueryable<T> ApplyOrderBy<T>(IQueryable<T> source, List<OrderByObject> orderBys)
{
IOrderedQueryable<T> orderedSource = null;
foreach (var orderBy in orderBys)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = typeof(T).GetProperty(orderBy.PropertyName);
if (property == null)
{
throw new ArgumentException($"Property '{orderBy.PropertyName}' not found on type '{typeof(T).Name}'.");
}
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var keySelector = Expression.Lambda(propertyAccess, parameter);
var methodName = orderBy.Ascending ? "OrderBy" : "OrderByDescending";
if (orderedSource != null)
{
methodName = orderBy.Ascending ? "ThenBy" : "ThenByDescending";
}
var resultExpression = Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), property.PropertyType },
orderedSource != null ? orderedSource.Expression : source.Expression,
Expression.Quote(keySelector));
orderedSource = (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExpression);
}
return orderedSource ?? source.AsQueryable().OrderBy(x => true);
}
}
3.测试接口如下:
[HttpPost]
public IActionResult DynamicQueryAndSort(QueryCondition queryCondition)
{
try
{
var productList = new List<Product>()
{
new Product{Id = 1,Name = "p1",Price = 11},
new Product{Id = 2,Name = "p2",Price = 12},
new Product{Id = 3,Name = "p3",Price = 13},
new Product{Id = 4,Name = "p4",Price = 14},
new Product{Id = 5,Name = "p5",Price = 15},
};
var result = productList.AsQueryable().DynamicQuery(queryCondition.QueryObjects, queryCondition.LogicalOperator, queryCondition.OrderBys).ToList();
return Ok(result);
}
catch (Exception ex)
{
throw;
}
}
4.接口入参如下:
{
"queryObjects": [
{
"propertyName": "Name",
"operator": "=",
"value": "p4"
},
{
"propertyName": "Name",
"operator": "=",
"value": "p5"
}
],
"orderBys": [
{
"propertyName": "Name",
"ascending": false
}
],
"logicalOperator": "or"
}