上一篇《使用Expression实现数据的任意字段过滤(1)》, 我们实现了通过CriteriaCollectionHandler对象来处理集合数据过滤。通过适当的扩展, 应该可以满足一般的筛选条件应用了。但是在我经历的项目中, 突然有个情况让我措手不及。下面和大家分享下。
这个项目叫WebAD,顾名思义, 就是将AD的管理界面使用Web来实现。 无可避免的要查询AD里面的对象,比如UserPrinciple,即查找某一OU节点下的所有用户。
先感受下UserPrinciple的属性
再感受下用户的气场:
“我需要用户的邮箱、部门名称、公司名称、密码修改时间…..”
好吧, 这些AD里有, UserPrincipal没有,但通过非公开的ExtensionGet()方法,这些变成可以有。
用户的气场再爆发:“我们还需要按邮箱名称、部门名称、公司名称来过滤!”
OMG,这合理,但,真的要这么做吗?
如果用传统的办法, 只能是按下面的步骤:
1) 从AD按OU节点查得所有的UserPrinciple
2) 将List<UserPrinciple>逐一转换为List<UserModel>, 其中UserModel包括了用户要的那些非公开属性, 通过使用反射取ExtensionGet()方法, 再调用该方法获得指定属性
3) 使用Linq对List<UserModel>进行过滤
大概试了下, 加入AD返回了2000个UserPrincipal对象, 每个对象取10个非公开属性,第2)步大概要用99%以上的时间。 为什么? 大量反射!这可不是EntityFramework,直接在SQLServer端就将过滤处理了,这是WebServer端干的活儿,严重拖累性能。
10分钟过去还得不到查询结果。 您再感受下客户气场……
解决的思路, 还是我们之前的ICollectionHandler,在将UserPrinciple 映射成UserModel之前, 先把过滤做了, 然后再把结果转换成UserModel, 这样能大大减少反射使用 的次数。可接下来的问题来了, 之前我们的CriteriaCollectionHandler类型中, 是使用type.GetProperty()方法来获取Public属性, 并进一步获取到属性值的,现在这些间接属性值改怎么办呢?
先来感受下代码:
1 public sealed class AdvanceCriteriaCollectionHandler : CriteriaCollectionHandler 2 { 3 private string PropertyKey { get; set; } 4 5 6 private MethodInfo GetPropertyMethod { get; set; } 7 8 9 public AdvanceCriteriaCollectionHandler(Func<object, string, object> getPropertyMethod, string propertyKey, object target, ComparerEnum comparer) 10 : base("", target, comparer) 11 { 12 GetPropertyMethod = getPropertyMethod.GetMethodInfo(); 13 this.PropertyKey = propertyKey; 14 } 15 16 17 private IQueryable<T> Filter<T>(IQueryable<T> source) 18 { 19 var type = typeof(T); 20 var parameter = Expression.Parameter(type, "p"); // 21 22 var constExpression = Expression.Constant(Target); // 转换为target的类型,以作比较 23 var propertyAccess = Expression.Call(GetPropertyMethod, parameter, Expression.Constant(PropertyKey)); 24 25 Expression comparisionExpression; 26 switch (Comparer) 27 { 28 case ComparerEnum.Eq: 29 comparisionExpression = Expression.Equal(propertyAccess, constExpression); 30 break; 31 case ComparerEnum.Ne: 32 comparisionExpression = Expression.NotEqual(propertyAccess, constExpression); 33 break; 34 case ComparerEnum.Lt: 35 comparisionExpression = Expression.LessThan(propertyAccess, constExpression); 36 break; 37 case ComparerEnum.Gt: 38 comparisionExpression = Expression.GreaterThan(propertyAccess, constExpression); 39 break; 40 case ComparerEnum.Le: 41 comparisionExpression = Expression.LessThanOrEqual(propertyAccess, constExpression); 42 break; 43 case ComparerEnum.Ge: 44 comparisionExpression = Expression.GreaterThanOrEqual(propertyAccess, constExpression); 45 break; 46 case ComparerEnum.StringLike: 47 if (!(Target is string)) 48 { 49 throw new NotSupportedException("StringLike is only suitable for string type property!"); 50 } 51 52 53 var stringContainsMethod = typeof(CriteriaCollectionHandler).GetMethod("StringContains"); 54 55 comparisionExpression = Expression.Call(stringContainsMethod, propertyAccess, constExpression); 56 57 break; 58 default: 59 comparisionExpression = Expression.Equal(propertyAccess, constExpression); 60 break; 61 } 62 63 64 var compareExp = Expression.Lambda(comparisionExpression, parameter); 65 var typeArguments = new Type[] { type }; 66 var methodName = "Where"; //sortOrder == SortDirection.Ascending ? "OrderBy" : "OrderByDescending"; 67 var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(compareExp)); 68 69 return source.Provider.CreateQuery<T>(resultExp); 70 } 71 72 public override ICollection<T> Execute<T>(ICollection<T> values) 73 { 74 var result = Filter(values.AsQueryable()).ToList(); 75 return result; 76 } 77 }
区别在哪呢? 构造函数中多了一个委托对象,这个委托对象就是用来处理取属性值的方法。三个参数分别对应了元素, 元素属性名称, 元素属性值(返回)。
其他的没有变化
使用示例(伪码)
1 var criteria1 = new AdvanceCriteriaCollectionHandler(PrincipalExtensions.GetExtension, "Department", "HR" , comparerEnum.Eq); // department = HR 2 var result = criteria1.Execute()
其中PrincipalExtensions.GetExtension 如下
1 public static class PrincipalExtensions
2 {
3 private static readonly MethodInfo ExtensionSet = typeof(Principal).GetMethod("ExtensionSet", BindingFlags.NonPublic | BindingFlags.Instance);
4 private static readonly ADSIUserHandler userHandler = new ADSIUserHandler();
5 public static void SetExtension<T>(this Principal principal, String key, T value)
6 {
7 ExtensionSet.Invoke(principal, new object[] { key, value });
8
9 }
10 private static readonly MethodInfo ExtensionGet = typeof(Principal).GetMethod("ExtensionGet", BindingFlags.NonPublic | BindingFlags.Instance);
11
12
13 public static T GetExtension<T>(this Principal principal, String key)
14 {
15 try
16 {
17 var values = (object[])ExtensionGet.Invoke(principal, new[] { key });
18
19 if (values == null || values.Length == 0)
20 {
21 return default(T);
22 }
23 return (T)values[0];
24
25 }
26 catch
27 {
28 return default(T);
29 }
30 }
31
32
33 public static object GetExtension(object principal, String key)
34 {
35
36 try
37 {
38 object[] values = (object[])ExtensionGet.Invoke(principal, new[] { key });
39 if (values == null || values.Length == 0)
40 {
41 return null;
42 }
43 return values[0];
44
45 }
46 catch
47 {
48 return null;
49 }
50 }
51
52
53 }
然后在获取所有UserPrinciple的后面立刻加上集合过滤(伪码):
1 ICollection<UserPrincipal> allUsers = ADSIHelper.GetUsers(recursive, searchBase, keywords).OfType<UserPrincipal>().ToList(); 2 3 4 if (advCriteria!= null) //advanced criteria collection handler 5 { 6 allUsers = advCriteria.Execute(allUsers); 7 }
return allUsers.ToModels();
重新估算下, 之前是2000*10 = 20,000次反射调用
现在,假如删选完成后只有100个结果, 则反射调用的次数是 2000*1 + 100*10 = 3,000 次反射调用。
不仅仅速度会明显变快,而且能和之前的CollectionHandler处理的思路保持一致,同样能支持任意字段的过滤。
使用Expression来实现对集合的任意字段过滤先介绍到这里。 除了筛选过滤,常见的还有排序和分页, 后面我再陆续介绍。