ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计
ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder
上节说到我们已经将表单转化为了QueryModel
并且将查询条件按我们的设计存为了ConditionItem。并且传递到了IQueryable.Where扩展方法中,对EF进行了查询:
当然,这里的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")为例
构造的过程为:
构建形如 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