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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值