随笔-50  评论-706  文章-1  trackbacks-0

QueryBuilder : 打造优雅的Linq To SQL动态查询(支持EF、.Net4)

大概两年前我写了篇《QueryBuilder : 打造优雅的Linq To SQL动态查询》,赢得不少厚爱,如今它还是我博客访问量最高的一篇(即使只有可怜的5500点击量,比一些大牛们少一位数)。时过境迁、岁月不饶人,当年得意之作现在看起来不值一文。收集一下朋友的反馈,主要有下列问题:

    1. 不支持 Entity Framework
    2. 不支持.Net 4
    3. Equals(c=>c.xx, null) 之前是被忽略掉,实际上应该解析成 c.xx is null。
    4. Between 没有考虑字符串的情况,例如 Between(c=>c.xx, ”A” , ”Z”) 则解析会报错。
    5. 部分不支持Nullable类型
    6. 其他没实现的功能:大于、小于、或…

 

——现在——

 

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来拼接是行不通的。

image

 

上一版本用Expression.Invoke解决,但Invoke在EF下不支持。

image

 

解决办法是改用 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


作者:Bruce编程的艺术世界
出处:http://coolcode.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted on 2011-06-08 21:58 CoolCode 阅读(2128) 评论(17) 编辑 收藏

评论:
#1楼 2011-06-08 22:13 | 陈陈#      
学习了。。
 回复 引用 查看   
#2楼 2011-06-08 22:56 | h_j_l      
Expression,现在恰好正在接触这话题
 回复 引用 查看   
#3楼 2011-06-09 08:24 | 幸运草      
这个要来顶一下,In 的问题困扰了好久了。谢谢兄弟!
 回复 引用 查看   
#4楼 2011-06-09 08:57 | huyong      
支持。
 回复 引用 查看   
#5楼 2011-06-09 09:02 | 麦舒      
感觉太繁琐了,没有 Linq to SQL 优雅。
 回复 引用 查看   
#6楼[楼主] 2011-06-09 09:59 | CoolCode      
引用幸运草:这个要来顶一下,In 的问题困扰了好久了。谢谢兄弟!

谢谢关注

 回复 引用 查看   
#7楼[楼主] 2011-06-09 10:03 | CoolCode      
引用麦舒:感觉太繁琐了,没有 Linq to SQL 优雅。


这个是为查询而生的,不是为了封装Linq to SQL

 回复 引用 查看   
#8楼 2011-06-09 10:12 | john23.net      
学习
 回复 引用 查看   
#10楼 2011-06-09 10:54 | 幸运草      
引用guozili@163.com:
还不如用:Dynamic LINQ
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Dynamic LINQ 是字符串,这是强类型。不一样哦!

 回复 引用 查看   
#11楼 2011-06-09 11:23 | 一周寂寞七天      
这个回复的样式挺爽的.......
 回复 引用 查看   
#12楼[楼主] 2011-06-09 12:09 | CoolCode      
@一周寂寞七天
呵呵,花了我不少心血移植过来的。。。

 回复 引用 查看   
#13楼 2011-06-09 23:13 | Apple Yang      
花样式?
 回复 引用 查看   
#14楼 2011-10-08 14:13 | waninlezu      
不能 有 join 么?

var queryBuilder = QueryBuilder.Create<Orders>()
.join<Custom>(Order.CustomID = Custom.CustomID )
.Like(c => c.Customers.ContactName, txtCustomer.Text)

.In(c => c.ShipCountry, selectedCountries);

 回复 引用 查看   
#15楼 2011-10-09 12:48 | Laro      
如果要增加一些查询条件,依然还是需要改代码,是否可以使用 IEnumerable<KeyValuePair<string, string>> 来作为入口的查询条件呢?
 回复 引用 查看   
#16楼 2011-12-28 13:53 | 心静如水-勇于面对      
恩,收藏一下,谢谢楼主!
 回复 引用 查看   
#17楼 2012-02-23 09:43 | Trrecy      
怎么还是没有大于小于呢?
 回复 引用 查看   
coolcode

网名:CoolCode,洋名:Bruce Lee,现就职于广州品高担任技术经理一职。以移动为行业方向,主要参与协同办公、工作流、知识库、门户等项目。工作之余一直有个小小愿望——重拾多年尘封的画笔到户外作画,但愿N年前的油画颜料还能用。吐舌笑脸


联系方式:
昵称:CoolCode
园龄:3年1个月
粉丝:77
关注:40

搜索

 

常用链接

我的标签

随笔分类(50)

随笔档案(50)

积分与排名

  • 积分 - 119611
  • 排名 - 837

最新评论

阅读排行榜

评论排行榜

推荐排行榜