Entity Framework解決sql 條件拼接,完美解決 解決 不支持 LINQ 表達式節點類型“Invoke”【轉】

傳統的操作數據庫方式,篩選數據需要用StringBuilder拼接一大堆的WHERE子句。

在Entity Framework中,代碼稍有不慎就會造成巨大性能消耗,如:

using(var db=new MyDbContext())

{

var s= db.Students.ToList().First(s=>s.ID=1200);

}

嘣!進行了全表數據讀取!當然一般人也不會犯這種低級的錯誤,言歸正傳。

可以簡單的這樣篩選數據:

 

using(var db=new MyDbContext())

{

var list =db.Students.AsQueryable();

if(********){list=list.Where(s=>s.ID=1200);}

if(******){list=list.Where(...)}

}

但是有時這種方法不能完成特定需求,如:

 

using(var db=new MyDbContext())

{

var list =db.Students.AsQueryable();

if(條件1){list=list.Where(s=>s.ID>1200);}

if(條件2){list=list.Where(s=>s.ID<1000);}

}

現在條件1和條件2同時成立,得到的是空結果集而不是ID>1200和ID<1000的結果集。

這只是兩個並列簡單條件的組合,如果是條件嵌套呢?

下面是假想:

using (var db = new MyDbContext())
            {


                Expression<Func<Student, bool>> checkStudent1 = s1 => s1.ID > 1200;
                Expression<Func<Student, bool>> checkStudent2 = s2 => s2.ID < 1000;
                var e =
                    Expression.Lambda<Func<Student, bool>>(
                        Expression.Or(checkStudent1.Body, checkStudent2.Body), checkStudent1.Parameters);
                var result = db.Students.Where(e).ToList();
            }

叫它假想的原因是執行會產生異常”The parameter 's2' was not bound in the specified LINQ to Entities query expression“。

e的內容是{s1 => ((s1.ID > 1200) Or (s2.ID < 1000))},很明顯s2這個參數是沒有被定義的。

實際上我們一直操作一個Student表,最終我們想要的也是多Lambda表達式合在一起對該Student表的操作。換句話說,s2應該用s1代替。

有人說了,這樣:

Expression<Func<Student, bool>> checkStudent1 = s => s.ID > 1200;
                Expression<Func<Student, bool>> checkStudent2 = s => s.ID < 1000;
                var e =
                    Expression.Lambda<Func<Student, bool>>(
                        Expression.Or(checkStudent1.Body, checkStudent2.Body), checkStudent1.Parameters);
                var result = db.Students.Where(e).ToList();

異常:”The parameter 's' was not bound in the specified LINQ to Entities query expression“。

e的內容是{s => ((s.ID > 1200) Or (s.ID < 1000))},現在參數都一樣是s了,但其實它們的GUID不同,也就是說它們還是兩個不同的參數。

我們需要做的是手工把checkStudent2.Body裏面的參數s換成checkStudent1.Body裏面的參數s。

ExpressionVisitor可以很好的完成這步操作。拿個別人現成的例子來用:

 

 
public class ParameterRebinder : ExpressionVisitor  
    {  
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;  
  
        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)  
        {  
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();  
        }  
  
        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)  
        {  
            return new ParameterRebinder(map).Visit(exp);  
        }  
  
        protected override Expression VisitParameter(ParameterExpression p)  
        {  
            ParameterExpression replacement;  
            if (map.TryGetValue(p, out replacement))  
            {  
                p = replacement;  
            }  
            return base.VisitParameter(p);  
        }  
    }  

 

 

更改後的測試代碼:

Expression<Func<Student, bool>> checkStudent1 = s => s.ID > 1200;
                Expression<Func<Student, bool>> checkStudent2 = s => s.ID < 1000;
                
                var body2 =
                    ParameterRebinder.ReplaceParameters(
                        checkStudent2.Parameters.Select((s,i)=>new{s,f=checkStudent1.Parameters[i]}).ToDictionary(p=>p.s,p=>p.f), checkStudent2.Body);
                var e =
                    Expression.Lambda<Func<Student, bool>>(
                        Expression.Or(checkStudent1.Body, body2), checkStudent1.Parameters);
                var result = db.Students.Where(e).ToList();

至此表達式順利拼接完成。當然這樣使用還是麻煩,借用別人的擴展類稍微修改一下:

 

public static class PredicateBuilder  
{  
  
    public static Expression<Func<T, bool>> True<T>() { return f => true; }  
    public static Expression<Func<T, bool>> False<T>() { return f => false; }  
    public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)  
    {  
        // build parameter map (from parameters of second to parameters of first)  
        var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);  
  
        // replace parameters in the second lambda expression with parameters from the first  
        var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);  
  
        // apply composition of lambda expression bodies to parameters from the first expression   
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);  
    }  
  
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)  
    {  
        return first.Compose(second, Expression.And);  
    }  
  
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)  
    {  
        return first.Compose(second, Expression.Or);  
    }  
}  

參考:http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章