ASP.NET MVC & EF 构建智能查询 三、解析QueryModel

ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计

ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder

上节说到我们已经将表单转化为了QueryModel

并且将查询条件按我们的设计存为了ConditionItem。并且传递到了IQueryable.Where扩展方法中,对EF进行了查询:

ASP.NET MVC & EF 构建智能查询 三、解析QueryModel

当然,这里的Where是一个IQueryable的扩展方法,其中调用了将QueryModel转换为Expression表达式的类QueryableSearcher。

   1: public static class QueryableExtensions
   2: {
   3:     /// <summary>
   4:     /// zoujian add , 使IQueryable支持QueryModel
   5:     /// </summary>
   6:     /// <typeparam name="TEntity"></typeparam>
   7:     /// <param name="table">IQueryable的查询对象</param>
   8:     /// <param name="model">QueryModel对象</param>
   9:     /// <param name="prefix">使用前缀区分查询条件</param>
  10:     /// <returns></returns>
  11:     public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> table, QueryModel model, string prefix = "") where TEntity : class
  12:     {
  13:         Contract.Requires(table != null);
  14:         return Where<TEntity>(table, model.Items, prefix);
  15:     }
  16:  
  17:     private static IQueryable<TEntity> Where<TEntity>(IQueryable<TEntity> table, IEnumerable<ConditionItem> items, string prefix = "")
  18:     {
  19:         Contract.Requires(table != null);
  20:         IEnumerable<ConditionItem> filterItems =
  21:             string.IsNullOrWhiteSpace(prefix)
  22:                 ? items.Where(c => string.IsNullOrEmpty(c.Prefix))
  23:                 : items.Where(c => c.Prefix == prefix);
  24:         if (filterItems.Count() == 0) return table;
  25:         return new QueryableSearcher<TEntity>(table, filterItems).Search();
  26:     }
  27: }

这里面我们调用的QueryableSearcher其实就是我们将QueryModel转为Expression表达式并进行查询返回IQueryable的核心类。

db.Users.Where(c => c.Id < 10 && (c.Name == "chhlgy" || c.Name == "chsword")).ToList();

中的表达式

c => c.Id < 10 && (c.Name == "chhlgy" || c.Name == "chsword")为例

构造的过程为:

ASP.NET MVC & EF 构建智能查询 三、解析QueryModel

构建形如 c=>Body 的表达式

我们使用如下方法,也就是QueryableSearcher类的入口Search方法

   1: public IQueryable<T> Search()
   2: {
   3:     //构建 c=>Body中的c
   4:     ParameterExpression param = Expression.Parameter(typeof(T), "c");
   5:     //构建c=>Body中的Body
   6:     var body = GetExpressoinBody(param, Items);
   7:     //将二者拼为c=>Body
   8:     var expression = Expression.Lambda<Func<T, bool>>(body, param);
   9:     //传到Where中当做参数,类型为Expression<Func<T,bool>>
  10:     return Table.Where(expression);
  11: }

1.构建参数 c

在上文中使用

ParameterExpression param = Expression.Parameter(typeof(T), "c"); 来构建了参数c

2.构建 Body

就是我们前面的GetExpressionBody方法所生成的

   1: private Expression GetExpressoinBody(ParameterExpression param, IEnumerable<ConditionItem> items)
   2: {
   3:     var list = new List<Expression>();
   4:     //OrGroup为空的情况下,即为And组合
   5:     var andList = items.Where(c => string.IsNullOrEmpty(c.OrGroup));
   6:     //将And的子Expression以AndAlso拼接
   7:     if (andList.Count() != 0)
   8:     {
   9:         list.Add(GetGroupExpression(param, andList, Expression.AndAlso));
  10:     }
  11:     //其它的则为Or关系,不同Or组间以And分隔
  12:     var orGroupByList = items.Where(c => !string.IsNullOrEmpty(c.OrGroup)).GroupBy(c => c.OrGroup);
  13:     //拼接子Expression的Or关系
  14:     foreach (IGrouping<string, ConditionItem> group in orGroupByList)
  15:     {
  16:         if (group.Count() != 0)
  17:             list.Add(GetGroupExpression(param, group, Expression.OrElse));
  18:     }
  19:     //将这些Expression再以And相连
  20:     return list.Aggregate(Expression.AndAlso);
  21: }

3.构建分组的逻辑关系 And/OR

也就是根据Or或And来拼接不同的Expression的GetGroupExpression方法

   1: private Expression GetGroupExpression(ParameterExpression param, IEnumerable<ConditionItem> items, Func<Expression, Expression, Expression> func)
   2: {
   3:     //获取最小的判断表达式
   4:     var list = items.Select(item => GetExpression(param, item));
   5:     //再以逻辑运算符相连
   6:     return list.Aggregate(func);
   7: }

4.构建分组的单元 单一的表达式 c.User<10

这里要获取三部分,分别是左侧的属性,这里的属性可能是多级

右侧的常量,这里的常量可能要有类型转换的问题,因为Expression要求类型

中间的判断符号或其它方法如Contains

   1: private Expression GetExpression(ParameterExpression param, ConditionItem item)
   2: {
   3:     //属性表达式
   4:     LambdaExpression exp = GetPropertyLambdaExpression(item, param);
   5:     //如果有特殊类型处理,则进行处理,暂时不关注
   6:     foreach (var provider in TransformProviders)
   7:     {
   8:         if (provider.Match(item, exp.Body.Type))
   9:         {
  10:             return GetGroupExpression(param, provider.Transform(item, exp.Body.Type), Expression.AndAlso);
  11:         }
  12:     }
  13:     //常量表达式
  14:     var constant = ChangeTypeToExpression(item, exp.Body.Type);
  15:     //以判断符或方法连接
  16:     return ExpressionDict[item.Method](exp.Body, constant);
  17: }

5.获取左侧的属性及类型

   1: private LambdaExpression GetPropertyLambdaExpression(ConditionItem item, ParameterExpression param)
   2: {
   3:     //获取每级属性如c.Users.Proiles.UserId
   4:     var props = item.Field.Split('.');
   5:     Expression propertyAccess = param;
   6:     var typeOfProp = typeof(T);
   7:     int i = 0;
   8:     do
   9:     {
  10:         PropertyInfo property = typeOfProp.GetProperty(props[i]);
  11:         if (property == null) return null;
  12:         typeOfProp = property.PropertyType;
  13:         propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
  14:         i++;
  15:     } while (i < props.Length);
  16:  
  17:     return Expression.Lambda(propertyAccess, param);
  18: }

6.获取操作符或方法

这里只列举了QueryMethod枚举的操作方法

   1: private static readonly Dictionary<QueryMethod, Func<Expression, Expression, Expression>> ExpressionDict =
   2:             new Dictionary<QueryMethod, Func<Expression, Expression, Expression>>
   3:                 {
   4:                     {
   5:                         QueryMethod.Equal,
   6:                         (left, right) => { return Expression.Equal(left, right); }
   7:                         },
   8:                     {
   9:                         QueryMethod.GreaterThan,
  10:                         (left, right) => { return Expression.GreaterThan(left, right); }
  11:                         },
  12:                     {
  13:                         QueryMethod.GreaterThanOrEqual,
  14:                         (left, right) => { return Expression.GreaterThanOrEqual(left, right); }
  15:                         },
  16:                     {
  17:                         QueryMethod.LessThan,
  18:                         (left, right) => { return Expression.LessThan(left, right); }
  19:                         },
  20:                     {
  21:                         QueryMethod.LessThanOrEqual,
  22:                         (left, right) => { return Expression.LessThanOrEqual(left, right); }
  23:                         },
  24:                     {
  25:                         QueryMethod.Contains,
  26:                         (left, right) =>
  27:                             {
  28:                                 if (left.Type != typeof (string)) return null;
  29:                                 return Expression.Call(left, typeof (string).GetMethod("Contains"), right);
  30:                             }
  31:                         },
  32:                     {
  33:                         QueryMethod.StdIn,
  34:                         (left, right) =>
  35:                             {
  36:                                 if (!right.Type.IsArray) return null;
  37:                                 //调用Enumerable.Contains扩展方法
  38:                                 MethodCallExpression resultExp =
  39:                                     Expression.Call(
  40:                                         typeof (Enumerable),
  41:                                         "Contains",
  42:                                         new[] {left.Type},
  43:                                         right,
  44:                                         left);
  45:  
  46:                                 return resultExp;
  47:                             }
  48:                         },
  49:                     {
  50:                         QueryMethod.NotEqual,
  51:                         (left, right) => { return Expression.NotEqual(left, right); }
  52:                         },
  53:                     {
  54:                         QueryMethod.StartsWith,
  55:                         (left, right) =>
  56:                             {
  57:                                 if (left.Type != typeof (string)) return null;
  58:                                 return Expression.Call(left, typeof (string).GetMethod("StartsWith", new[] {typeof (string)}), right);
  59:  
  60:                             }
  61:                         },
  62:                     {
  63:                         QueryMethod.EndsWith,
  64:                         (left, right) =>
  65:                             {
  66:                                 if (left.Type != typeof (string)) return null;
  67:                                 return Expression.Call(left, typeof (string).GetMethod("EndsWith", new[] {typeof (string)}), right);
  68:                             }
  69:                         },
  70:                     {
  71:                         QueryMethod.DateTimeLessThanOrEqual,
  72:                         (left, right) => { return Expression.LessThanOrEqual(left, right); }
  73:                         }
  74:                 };

7.将Value中的值转为目标类型

   1: /// <summary>
   2: /// 类型转换,支持非空类型与可空类型之间的转换
   3: /// </summary>
   4: /// <param name="value"></param>
   5: /// <param name="conversionType"></param>
   6: /// <returns></returns>
   7: public static object ChangeType(object value, Type conversionType)
   8: {
   9:     if (value == null) return null;
  10:     return Convert.ChangeType(value, TypeUtil.GetUnNullableType(conversionType));
  11: }
  12:  
  13: /// <summary>
  14: /// 转换SearchItem中的Value的类型,为表达式树
  15: /// </summary>
  16: /// <param name="item"></param>
  17: /// <param name="conversionType">目标类型</param>
  18: public static Expression ChangeTypeToExpression(ConditionItem item, Type conversionType)
  19: {
  20:     if (item.Value == null) return Expression.Constant(item.Value, conversionType);
  21:     #region 数组
  22:     if (item.Method == QueryMethod.StdIn)
  23:     {
  24:         var arr = (item.Value as Array);
  25:         var expList = new List<Expression>();
  26:         //确保可用
  27:         if (arr != null)
  28:             for (var i = 0; i < arr.Length; i++)
  29:             {
  30:                 //构造数组的单元Constant
  31:                 var newValue = ChangeType(arr.GetValue(i), conversionType);
  32:                 expList.Add(Expression.Constant(newValue, conversionType));
  33:             }
  34:         //构造inType类型的数组表达式树,并为数组赋初值
  35:         return Expression.NewArrayInit(conversionType, expList);
  36:     }
  37:  
  38:     #endregion
  39:  
  40:     var elementType = TypeUtil.GetUnNullableType(conversionType);
  41:     var value = Convert.ChangeType(item.Value, elementType);
  42:     return Expression.Constant(value, conversionType);
  43: }

源代码下载:

http://efsearchmodel.codeplex.com/releases/view/63921

上一篇:Linux相关(一):linux主要目录速查表


下一篇:亲手打造自己的 Linux 桌面环境