动态拼接LINQ查询条件的解决方案

时间:2021-01-23 19:10:54

开发中,需要根据用户选择的过滤条件从数据库中查询数据。过滤条件包括等于(”==”)、不等于(“!=”)、大于(“>”)、小于(”<”)、大于等于(“>=”)、小于等于(“<=”)、包含(”in”)、不包含(”out”)八种。过滤条件之间可以是“AND”和“OR”两种关系。过滤条件支持的数据类型有整型和字符串等。用户选择的过滤条件通过json数据提交给后端,后端反序列化后得到过滤条件对象,该对象是FilterModel类的实例,下面是FilterModel类的定义。

    public class FilterModel
{
public string column { get; set; }//过滤条件中使用的数据列
public string action { get; set; }//过滤条件中的操作:==、!=等
public string logic { get; set; }//过滤条件之间的逻辑关系:AND和OR
public string value { get; set; }//过滤条件中的操作的值
public string dataType { get; set; }//过滤条件中的操作的字段的类型
}

数据库操作使用ADO.NET EF技术,查询使用Linq。而Linq的扩展方法接收的是一个lambda表达式作为查询条件。那么怎样可以将用户选择的查询条件转换成需要的lambda表达式呢?本文给出了自己的解决方案。
首先,定义下面的泛型方法GetFilterExpression,该方法完成lambda表达式的拼接。GetFilterExpression方法中泛型参数T为需要操作的数据库表对象,输入参数为FilterModel类的实例集合,包含用户选择的所有条件,遍历该集合,调用CreateLambda()方法生成条件对应的lambda表达式,虽有根据条件之间的关系是“AND”还是“OR”完成lambda表达式的拼接工作。

        public static Expression<Func<T, bool>> GetFilterExpression<T>(List<FilterModel> filterConditionList)
{
Expression<Func<T, bool>> condition = null;
try
{
if (filterConditionList != null && filterConditionList.Count > 0)
{
foreach (FilterModel filterCondition in filterConditionList)
{
Expression<Func<T, bool>> tempCondition = CreateLambda<T>(filterCondition);
if (condition == null)
{
condition = tempCondition;
}
else
{
if ("AND".Equals(filterCondition.logic))
{
condition = condition.And(tempCondition);
}
else
{
condition = condition.Or(tempCondition);
}
}
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogException("获取筛选条件异常:" + ex.Message);
}
return condition;
}

接下来,介绍CreateLambda()方法的逻辑,该方法完成过滤条件到lambda表达式的生成操作。

        public static Expression<Func<T, bool>> CreateLambda<T>(FilterModel filterCondition)
{
var parameter = Expression.Parameter(typeof(T), "p");//创建参数i
var constant = Expression.Constant(filterCondition.value);//创建常数
MemberExpression member = Expression.PropertyOrField(parameter, filterCondition.column);
if ("=".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.Equal(member, constant), parameter);
}
else if ("!=".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.NotEqual(member, constant), parameter);
}
else if (">".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(member, constant), parameter);
}
else if ("<".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.LessThan(member, constant), parameter);
}
else if (">=".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual(member, constant), parameter);
}
else if ("<=".Equals(filterCondition.action))
{
return Expression.Lambda<Func<T, bool>>(Expression.LessThanOrEqual(member, constant), parameter);
}
else if ("in".Equals(filterCondition.action) && "1".Equals(filterCondition.dataType))
{
return GetExpressionWithMethod<T>("Contains", filterCondition);
}
else if ("out".Equals(filterCondition.action) && "1".Equals(filterCondition.dataType))
{
return GetExpressionWithoutMethod<T>("Contains", filterCondition);
}
else
{
return null;
}
}

CreateLambda()方法根据判断条件的类型,调用Expression的Equal、NotEqual、GreaterThan、LessThan、LessThanOrEqual、GreaterThanOrEqual方法来生成lambda表达式,而对于”包含”和”不包含”,需要调用字符串的Contains()方法来完成。而这部分逻辑在GetExpressionWithMethod()和GetExpressionWithoutMethod()方法中实现。
“不包含”是在可看成是” 包含”的否定(Not)。

        public static Expression<Func<T, bool>> GetExpressionWithMethod<T>(string methodName, FilterModel filterCondition)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
MethodCallExpression methodExpression = GetMethodExpression(methodName, filterCondition.column, filterCondition.value, parameterExpression);
return Expression.Lambda<Func<T, bool>>(methodExpression, parameterExpression);
}

public static Expression<Func<T, bool>> GetExpressionWithoutMethod<T>(string methodName, FilterModel filterCondition)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
MethodCallExpression methodExpression = GetMethodExpression(methodName, filterCondition.column, filterCondition.value, parameterExpression);
var notMethodExpression = Expression.Not(methodExpression);
return Expression.Lambda<Func<T, bool>>(notMethodExpression, parameterExpression);
}

/// <summary>
/// 生成类似于p=>p.values.Contains("xxx");的lambda表达式
/// parameterExpression标识p,propertyName表示values,propertyValue表示"xxx",methodName表示Contains
/// 仅处理p的属性类型为string这种情况
/// </summary>
/// <param name="methodName"></param>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
/// <param name="parameterExpression"></param>
/// <returns></returns>
private static MethodCallExpression GetMethodExpression(string methodName, string propertyName, string propertyValue, ParameterExpression parameterExpression)
{
var propertyExpression = Expression.Property(parameterExpression, propertyName);
MethodInfo method = typeof(string).GetMethod(methodName, new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
return Expression.Call(propertyExpression, method, someValue);
}

最后,介绍一下拼接lambda表达式的实现代码。LinqBuilder类提供 Or()和And()来拼接关系是”且”和”或”的lambda表达式。这部分代码来源于网络。

    public static class LinqBuilder
{
/// <summary>
/// 默认True条件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Expression<Func<T, bool>> True<T>() { return f => true; }

/// <summary>
/// 默认False条件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Expression<Func<T, bool>> False<T>() { return f => false; }

/// <summary>
/// 拼接 OR 条件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exp"></param>
/// <param name="condition"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> condition)
{
var inv = Expression.Invoke(condition, exp.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.Or(exp.Body, inv), exp.Parameters);
}

/// <summary>
/// 拼接And条件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="exp"></param>
/// <param name="condition"></param>
/// <returns></returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> condition)
{
var inv = Expression.Invoke(condition, exp.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.And(exp.Body, inv), exp.Parameters);
}
}