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

开发中,需要根据用户选择的过滤条件从数据库中查询数据。过滤条件包括等于(”==”)、不等于(“!=”)、大于(“>”)、小于(”<”)、大于等于(“>=”)、小于等于(“<=”)、包含(”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和ORpublic 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");//创建参数ivar 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);}}