傳統的操作數據庫方式,篩選數據需要用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
完美的動態拼接Lambda表達式如下: using (var db = new MyDbContext())
{
var predicate = PredicateBuilder.True<Student>();
predicate=predicate.And(s => s.ID > 1200);
predicate=predicate.Or(s => s.ID < 1000);
var result = db.Students.Where(predicate).ToList();
}
下面是一個我自己使用的例子,僅供參考:
using (var db = new SHTrackerDbContext()) { var predicate = PredicateBuilder.True<Course>(); settings = DecorateSettings(settings); Expression<Func<Course, bool>> checkCourse = c => db.Students.Any(s => s.CourseID == c.ID); if (!string.IsNullOrEmpty(settings.Quater_Year)) { checkCourse = c => db.Students.Any(s => s.CourseID == c.ID && db.Student2CBOs.Any( s2c => s2c.StudentID == s.ID && s2c.Quater_Year.Equals(settings.Quater_Year))); } if (settings.QuaterYearArray != null) { checkCourse = c => db.Students.Any(s => s.CourseID == c.ID && db.Student2CBOs.Any( s2c => s2c.StudentID == s.ID && settings.QuaterYearArray.Any(qy => qy.Equals(s2c.Quater_Year)))); } if (!string.IsNullOrEmpty(settings.DPU_ID)) { checkCourse = checkCourse.And( c => db.Students.Any(s => s.CourseID == c.ID && s.DPU_ID.Equals(settings.DPU_ID))); } predicate = predicate.And(checkCourse); if (settings.IsCheckInstructorName) { predicate = predicate.And(c => c.InstructorName.Equals(settings.InstructorName)); } if (!string.IsNullOrEmpty(settings.Term)) { predicate = predicate.And(c => c.TermDescription.Equals(settings.Term)); } if (settings.TermArray != null) { predicate = predicate.And(c => settings.TermArray.Any(t => t.Equals(c.TermDescription))); } if (settings.CourseType != CourseType.All) { predicate = predicate.And(c => c.Type == (int) settings.CourseType); } var cc = new CourseCollection( db.Courses.AsNoTracking() .Where(predicate) .OrderByDescending(m => m.ID) .Skip((pageIndex - 1)*pageSize) .Take(pageSize) .ToList(), db.Courses.AsNoTracking().Where(predicate).Count()) { PageIndex = pageIndex, PageSize = pageSize, Settings = DecorateSettings(settings) }; return cc; }
參考:http://blog.csdn.net/leewhoee/article/details/8968023