下面是C#用表达式树构建动态查询的完整攻略。
什么是表达式树
表达式树(Expression Tree)是将操作表达式按照层级结构组成的一种数据结构,类似于抽象语法树(AST)。在C#中,表达式树可以动态表示Lambda表达式的结构。
为何要用表达式树构建动态查询
在很多情况下,我们需要设计一个通用的、可扩展的查询条件表达式,比如一个动态搜索框,用户可以在其中输入部分查询条件,程序应该根据用户输入生成对应的查询语句。此时,表达式树的动态构建能力可以帮助我们实现这一需求。
如何用表达式树构建动态查询
以下是具体的步骤。
第一步:定义查询参数类
首先,我们需要先定义一个查询参数类,它包含了我们需要查询的表的所有属性,并且这些属性都是可空的。代码示例:
public class QueryParams
{
public string? Name { get; set; }
public int? Age { get; set; }
public string? Gender { get; set; }
// 其他属性
}
第二步:根据参数动态构建表达式树
接下来,我们需要根据用户输入的查询参数动态构建表达式树。以上面的查询参数类为例,查询条件可以是多个条件组成的逻辑与(And)关系。我们可以像下面这样构建表达式树:
public Expression<Func<UserInfo, bool>> BuildQueryExpression(QueryParams queryParams)
{
// 创建 UserInfo 的参数表达式
var userInfoParam = Expression.Parameter(typeof(UserInfo), "u");
// 创建一个空的查询表达式,作为操作的基础
Expression queryExpr = Expression.Constant(true);
// 根据查询参数构建查询表达式
if (!string.IsNullOrEmpty(queryParams.Name))
{
var nameExpr = Expression.Equal(Expression.Property(userInfoParam, nameof(UserInfo.Name)), Expression.Constant(queryParams.Name));
queryExpr = Expression.AndAlso(queryExpr, nameExpr);
}
if (queryParams.Age.HasValue)
{
var ageExpr = Expression.Equal(Expression.Property(userInfoParam, nameof(UserInfo.Age)), Expression.Constant(queryParams.Age.Value));
queryExpr = Expression.AndAlso(queryExpr, ageExpr);
}
if (!string.IsNullOrEmpty(queryParams.Gender))
{
var genderExpr = Expression.Equal(Expression.Property(userInfoParam, nameof(UserInfo.Gender)), Expression.Constant(queryParams.Gender));
queryExpr = Expression.AndAlso(queryExpr, genderExpr);
}
// 将查询表达式和参数表达式组合成 Lambda 表达式
return Expression.Lambda<Func<UserInfo, bool>>(queryExpr, userInfoParam);
}
以上代码通过循环遍历查询参数,根据参数组合运算构建查询表达式。同时,Expression.Lambda方法将表达式和参数组合成了一个Lambda表达式,得到一个可以用于Linq查询的查询条件。
第三步:用Linq查询结果
最后,我们可以拿着得到的查询条件,使用Linq查询数据源,比如:
using (var db = new DbContext())
{
var userInfoQuery = db.UserInfo.AsQueryable();
var queryParams = new QueryParams { Name = "张三", Age = 18, Gender = "Male" };
var queryExpr = BuildQueryExpression(queryParams);
var result = userInfoQuery.Where(queryExpr).ToList();
// 处理查询结果
}
两条示例说明
以下是两个使用表达式树动态构造查询条件的示例。
示例一:动态构造Like查询条件
假设我们需要实现一个模糊查询,可以匹配一张表中的多个属性字段。在Linq中,模糊查询可以用like关键字实现,但是每个属性都需要构造一遍like查询表达式,非常麻烦。通过表达式树的动态构造能力,我们可以实现动态的多属性模糊查询。代码示例:
public Expression<Func<TEntity, bool>> BuildLikeExpression<TEntity>(params Expression<Func<TEntity, object>>[] propertySelectors)
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var constants = new List<ConstantExpression>();
var body = propertySelectors.Aggregate<UnaryExpression, Expression>(null, (current, selector) =>
{
var property = (PropertyInfo)((MemberExpression)selector.Body).Member;
var constant = Expression.Constant($"%{search}%");
if (property.PropertyType != typeof(string) && property.PropertyType != typeof(Guid))
{
throw new ArgumentException("属性类型必须为string或Guid");
}
if (property.PropertyType == typeof(Guid))
{
constant = Expression.Convert(constant, typeof(Guid));
}
constants.Add(constant);
var left = Expression.Call(CreateToUpperMethodCallExpression(selector), typeof(string).GetMethod("Contains", new[] { typeof(string) }), constant);
return current == null ? left : Expression.Or(current, left);
});
var lambda = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
return lambda;
}
private static MethodCallExpression CreateToUpperMethodCallExpression(Expression expression)
{
if (!(expression is MemberExpression memberExpression))
{
throw new ArgumentException("selector必须是MemberExpression类型");
}
var property = (PropertyInfo) memberExpression.Member;
if (property.PropertyType != typeof(string))
{
throw new ArgumentException("属性类型必须为string");
}
var toUpperMethod = typeof(string).GetMethod("ToUpper", new Type[0]);
var propertyAccess = Expression.MakeMemberAccess(expression, property);
return Expression.Call(propertyAccess, toUpperMethod);
}
以上代码中,我们定义了一个BuildLikeExpression方法,它接受一个或多个属性选择器作为参数,用于构建Like查询条件。该方法利用了表达式树的组合能力,将多个查询属性的like表达式组合成一个复合表达式,并返回与该复合表达式等效的Lambda表达式。
示例二:动态构造多关键词查询
假如我们需要实现一个多关键词查询,用户输入的查询条件可以是多个关键词组成的逻辑"或"(Or)关系。在Linq中,多关键词查询可以用多个Contains关键字实现,但是每个查询关键字都需要构造对应的Contains查询表达式,非常繁琐。通过表达式树的动态构造能力,我们可以实现动态的多关键词查询。代码示例:
public Expression<Func<TEntity, bool>> BuildMultipleContainsExpression<TEntity>(string[] keywordArray, params Expression<Func<TEntity, object>>[] propertySelectors)
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var constants = new List<ConstantExpression>();
var body = keywordArray.Aggregate<Expression>(null, (current, keyword) =>
{
var contain = propertySelectors.Aggregate<UnaryExpression, Expression>(null, (current1, selector) =>
{
var property = (PropertyInfo)((MemberExpression)selector.Body).Member;
var constant = Expression.Constant($"%{keyword}%");
if (property.PropertyType != typeof(string) && property.PropertyType != typeof(Guid))
{
throw new ArgumentException("属性类型必须为string或Guid");
}
if (property.PropertyType == typeof(Guid))
{
constant = Expression.Convert(constant, typeof(Guid));
}
constants.Add(constant);
var left = Expression.Call(CreateToUpperMethodCallExpression(selector), typeof(string).GetMethod("Contains", new[] { typeof(string) }), constant);
return current1 == null ? left : Expression.Or(current1, left);
});
return current == null ? contain : Expression.And(current, contain);
});
var lambda = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
return lambda;
}
以上代码中,我们定义了一个BuildMultipleContainsExpression方法,它接受一个或多个关键词和一个或多个属性选择器作为参数,用于构建多关键词查询表达式。该方法利用了表达式树的组合能力,将多个关键词和多个查询属性的Expression。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#用表达式树构建动态查询的方法 - Python技术站