Linq to Entity經驗:動態查詢


    上篇文章(Linq to Entity經驗:表達式轉換)我分享了在使用Ling to Entity時,遇到的一個表達式轉換問題,其主要解決的是讓UI層調用數據查詢時能夠實現最大程度上的封裝,使得我們的業務邏輯層在處理數據查詢時更為精簡,不再需要每一個條件寫段邏輯。這篇我來總結下我們項目是中如何處理動態條件查詢的問題。
   
    問題:

    如何解決動態條件查詢,而繼續保證業務邏輯層的穩定性?
    場景:

    搜索學生信息,我們可能按學號搜索,也可能按姓名搜索,還有可能按班級搜索,當然也有可能是其它條件,最復雜的情況是同時按多個條件查詢。
   
    傳統解決方案:

    遇到這種情況,基本上有兩種類似的方法:
   
    1:拼接動態SQL
    因為不知道查詢條件,所以可以采用拼接SQL字符串的形式來完成,它的缺點如下:
    缺點一:需要注意SQL注入,盡管我們可以采用參數化來解決。
    缺點二:需要人工去做這件事情。
    缺點三:這樣的需求多了,也會大大增加程序員的工作量。
   
    2:大的通用性存儲過程
    在存儲過程中定義多個參數,然后在存儲過程內部判斷使用哪些條件,這個方案也有缺點:
    缺點一:程序中需要寫大量這種有動態條件查詢需求的存儲過程,且邏輯相對復雜。
    缺點二:存儲過程有自身的一些缺點,這里就不多講了。
   
    解決方案:

    充分利用表達式樹的作用來動態構建查詢條件,以保證業務邏輯層的穩定性,減輕工作量。
   
    下面是我們業務邏輯層的查詢接口方法:
    

IList<ObjectModel.ActionInfo> QueryByPage<TKey>(Expression<Func<ObjectModel.ActionInfo,  bool>> filter, Expression<Func<ObjectModel.ActionInfo, TKey>> orderBy,  int orderType,  int pageSize,  int pageIndex,  out  int recordsCount)

    
    優點:
    1:無論UI上的條件是什么,只要在UI層構建好查詢表達式,業務邏輯層的查詢接口是不需要變更的。
    2:避免在條件中使用字符串,之前提到的兩種方法都需要傳遞條件以確定最終的表字段信息,這是極其不高明的。
    3:基於Linq式的查詢,使得程序員更加容易理解及接受。
   
    注意:
    這里定義的查詢接口,是以一張主鍵為基礎的,只要定義好相關的關聯表,無論怎樣復雜,此接口都不需要額外編寫方法。
    比如有一個學生表:Student,學生表有一個外鍵列ClassId,對應的是班級表,我們可以這樣寫查詢:if(班級!="") p=>p.Class.Name=="初二2008班" 即查詢初二2008班所有學生信息。但它不能解決某些特別復雜的查詢.需要按情況來決定。
   
    調用示例:

    這里先看下最終的效果。我們可以定義And,還可以定義Or,如果有需要還可以擴展其方法。
    

Expression<Func<AllocationPlan,  bool>> predicate = p => p.IsActive;
             if (planCondition.Project !=  0) { predicate = predicate.And(c => c.ProjectId == planCondition.Project); }
             if (planCondition.PlanType !=  0) { predicate = predicate.And(c => c.AllocationTypeId.Value == planCondition.PlanType); }

 

    方案原理:
    將兩個表達式合並在一起,其實無論如何組織條件,超不出兩類常見的表達式:
    1:And,對應SQL中的=,比如Where Name="Tom",它可以將多個條件And在一起變成 Where Name="Tome" and ClassId=1
    2:Or,對應SQL中的or,比如 Where EmployeeId=0 or EmployeeId=2
   
    這里不討論SQL中的一些高級函數用法,只解決常見問題,下面是一老外寫的,能夠很好的解決動態條件查詢時的表達式創建問題,可供參考:
    

public  static  class PredicateBuilderUtility
{
     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.AndAlso);
    }
     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);
    }
}

 

    這里有一個需要特別注意,就是多個表達式中的參數問題,有的時候將多個表達式合並在一起后,雖然程序中看起來沒有什么問題,但當EntityFramwork執行數據庫查詢時會提示:參數p沒有綁定之類的異常信息,它的目的就是統一多個表達式中的參數p。
    比如:
    表達式1:Expression<Func<AllocationPlan, bool>> predicate = p => p.IsActive;
    表達式1:Expression<Func<AllocationPlan, bool>> predicate2 = p => p.Id>0;
    某些情況下我們需要將上面兩個表達式合並成一個,然后調用數據庫查詢,處理不當就會出現上面的錯誤。
    

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);
    }
}

    

     總結:
     有了表達式合並的工具類,再結合倉儲接口,我們可以寫出簡單容易理解動態條件查詢的程序,也解決了其它傳統方案的一些缺點,但這種方案自身也可能有自身的適用場景,適用自身項目的就是最優的,這是我的座右銘。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM