zoukankan      html  css  js  c++  java
  • ASP.NET MVC & EF 构建智能查询 三、解析QueryModel

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

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

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

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

    image

    当然,这里的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")为例

    构造的过程为:

    image

    构建形如 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

  • 相关阅读:
    [转]MongoDB for Java】Java操作MongoDB
    [转]Apache Maven 入门篇 ( 上 )
    [转]Apache Maven 入门篇(下)
    [转]-Dmaven.multiModuleProjectDirectory system propery is not set. 解决方案 适用于myeclipes 和 eclipes
    [转]java代码注释规范
    [转] java编程规范
    [原创]java WEB学习笔记43:jstl 介绍,core库详解:表达式操作,流程控制,迭代操作,url操作
    [转]详解Java解析XML的四种方法
    [原创]java WEB学习笔记42:带标签体的自定义标签,带父标签的自定义标签,el中自定义函数,自定义标签的小结
    [原创]java WEB学习笔记41:简单标签之带属性的自定义标签(输出指定文件,计算并输出两个数的最大值 demo)
  • 原文地址:https://www.cnblogs.com/chsword/p/searchmodel_3.html
Copyright © 2011-2022 走看看