大概两年前我写了篇《QueryBuilder : 打造优雅的Linq To SQL动态查询》,赢得不少厚爱,如今它还是我博客访问量最高的一篇(即使只有可怜的5500点击量,比一些大牛们少一位数)。时过境迁、岁月不饶人,当年得意之作现在看起来不值一文。收集一下朋友的反馈,主要有下列问题:
-
- 不支持 Entity Framework
- 不支持.Net 4
- Equals(c=>c.xx, null) 之前是被忽略掉,实际上应该解析成 c.xx is null。
- Between 没有考虑字符串的情况,例如 Between(c=>c.xx, ”A” , ”Z”) 则解析会报错。
- 部分不支持Nullable类型
- 其他没实现的功能:大于、小于、或…
——现在——
QueryBuilder已经发生翻天覆地的变化,但对于使用者来说,使用接口还是一成不变的,并且完全兼容上一版本。当然,它依旧是开源&免费的!
var queryBuilder = QueryBuilder.Create<Orders>() .Like(c => c.Customers.ContactName, txtCustomer.Text) .Like(c => c.Customers.CompanyName, txtCustomer.Text) .Between(c => c.OrderDate, DateTime.Parse(txtDateFrom.Text), DateTime.Parse(txtDateTo.Text)) .Equals(c => c.EmployeeID, int.Parse(ddlEmployee.SelectedValue)) .In(c => c.ShipCountry, selectedCountries);
技术分析
Lambda表达式的参数作用域
难点在于不同查询条件之间的参数作用域是相对独立的,如果直接使用 Expression.AndAlso来拼接是行不通的。
上一版本用Expression.Invoke解决,但Invoke在EF下不支持。
解决办法是改用 ExpressionVisitor,重写VisitParameter,返回新的参数表达式。
public class ParameterExpressionVisitor : ExpressionVisitor { private ParameterExpression newParameterExpression; public ParameterExpressionVisitor(ParameterExpression p) { newParameterExpression = p; } public Expression ChangeParameter(Expression exp) { return Visit(exp); } protected override Expression VisitParameter(ParameterExpression p) { return newParameterExpression; } }
也即是获取第一个Lambda表达式的参数,把后面的表达式用该参数替换。
private static Expression GetMemberExpression<T, P>(IQueryBuilder<T> q, Expression<Func<T, P>> property) { if (q.Parameters == null || q.Parameters.Length == 0) { q.Parameters = property.GetParameters(); return property.Body; } ParameterExpressionVisitor visitor =new ParameterExpressionVisitor(q.Parameters[0]); Expression memberExpr = visitor.ChangeParameter(property.Body); return memberExpr; }
In操作
旧版本的In操作不支持EF,改成通过反射获取泛型方法。
…… Expression<Func<P[], P, bool>> InExpression = (list, el) => list.Contains(el); var methodExp = InExpression;var invoke = Expression.Invoke(methodExp, constant, property.Body);Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(invoke, parameter); ……
其中有重载的泛型方法的获取比较麻烦,大家可以观摩一下代码,看看有无好建议。
//var method = typeof(Enumerable).GetMethod("Contains"); //因为有重载,这样获取不到 private static MethodInfo method_Contains = (from m in typeof(Enumerable).GetMethods() where m.Name.Equals("Contains") && m.IsGenericMethod && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 2 select m ).First();
Like操作
旧版本的Like操作使用SqlMethods.Like,也不支持EF。新版本改成字符串的Contains。
typeof(string).GetMethod("Contains", new Type[] { typeof(string) })
Between操作
旧版本的Between不支持字符串,例如 Between(c=>c.xx, ”A” , ”Z”) 则解析会报错。新版本增加新的扩展方法单独处理字符串的情况。
Between<T>(this IQueryBuilder<T> q, Expression<Func<T, string>> property, string from, string to)
小结
很难得两年写的东东在今时今日还能保持原有接口不变并成功重构啊,已经激动到内牛满面&裸奔ing…
源代码下载 CoolCode.Linq.V2.rar
参考
http://www.cnblogs.com/coolcode/archive/2009/09/28/IQueryBuilder.html