Linq to Entity經驗:表達式轉換(修正版本)


      之前有篇文章(Linq to Entity經驗:表達式轉換)我分享過表達式轉換的問題,當時以為問題解決了,但后來實際應用中發現其實沒有解決,也並非完全沒有解決,只不過不實用,問題如下:
 
      在講問題之前,先來看看表達式轉換的目的


      其實我們這么費勁的進行表達式轉換,就是為了讓我們在UI層寫的表達式樹條件能夠最終轉換成EntityFramework能夠識別的表達式,之所以需要轉換,那是因為EntityFramework只能識別EntityFramework自身的實體對象,如果我們是數據庫優先的話,這些實體就是自動生成的,而我們的業務系統往往有獨立於數據庫的業務實體,這兩者有些情況下相同,有些情況下非常不相同,這是我們需要轉換的原因,比如TestExpression.Model.Courier是業務實體,My.FrameWork.Utilities.Test.Courier是數據庫對象,看如下需求:


      UI層進行數據搜索數據的條件搜集:
      

Expression<Func<TestExpression.Model.Courier,  bool>> two2 = c => c.Value.StartsWith(mes.Name);

 

      要想進行數據庫查詢,就需要將上面的表達式樹轉換成下面EntityFramework能夠識別的表達式樹。注意,這兩個表達式雖然對象名稱一樣,但命名空間不同。
  

Expression<Func<My.FrameWork.Utilities.Test.Courier,  bool>> two2 = c => c.Value.StartsWith(mes.Name);

  
      問題:

      StartsWith的參數是字符串,我們知道這個字符串和其它大多數基元數據不一樣,它不是值類型,而是引用類型,所以當外面的局部變量做為參數傳遞到表達式中時,在表達式樹進行解析時還會發生MemberAccess,而上篇文章中只支持參數類型是ConstantExpression,而這里的MemberExpression在解析時就是報異常。,比如下面的表達式是可以轉換的,直接傳遞"min",這就是一個ConstantExpression類型的參數:
  

Expression<Func<TestExpression.Model.Courier,  bool>> two2 = c => c.Value.StartsWith( " min ");

 

      但如果這樣寫就不行:
  

  string name= " min ";
  Expression<Func<TestExpression.Model.Courier,  bool>> two2 = c => c.Value.StartsWith(name);

     

      以及文章開關提到的表達式也不行:(下面的mes是一個class)

Expression<Func<TestExpression.Model.Courier,  bool>> two2 = c => c.Value.StartsWith(mes.Name);

       
     如何解決?這里分享下我解決問題的過程。
 
    1:既然參數是ConstantExpression的情況可行,那么能否將原本不是ConstantExpression的參數變更為ConstantExpression。
        有了這個想法,當時認為既然mes.Name是一個對象的屬性,如果對這個屬性值進行下字符串的深度復制是不是也就擺脫class的引用類型問題了,雖然擺脫了class的問題,但發現string本身就是一個引用類型,所以無論是否轉換還是沒能將非ConstantExpression轉換成ConstantExpression的情況,也就是說下面的處理是徒勞無功的。

 

    public  static  class StringDeepClone
    {
         public  static  string GetStringDeepClone( this  string source)
        {
             string result =  string.Empty;
             using (MemoryStream ms =  new MemoryStream())
            {
                BinaryFormatter bf =  new BinaryFormatter();
                bf.Serialize(ms, source );
                source =  null;
                ms.Position =  0;
                result = (( string)bf.Deserialize(ms));
            }
             return result;
        }
    }


    2:既然引用類型的問題是會發生MemberAccess,那么是否能夠解析帶局部變量的MemberExpression?
    

Expression<Func<TestExpression.Model.Courier,  bool>> two2 = c => c.Value.StartsWith(mes.Name);

        像這行代碼,在整個表達式的上下文中是沒有mes類型信息的,它是一個外部的局部變量,這樣在解析時就會增大難度,后來我咨詢過腦袋(http://cnblogs.com/ninputer),他給我提供了一個轉換方法,后來由於代碼不全就沒繼續研究了,代碼的功能是可以解析這種帶局部變量的MemberExpression。
    
    3:既然解析MemberExpression沒能成功,那么是否能夠從MemberExpression中直接抽取出值來呢?
         在網上搜索了一番,發現一老外提供了一個方法能夠解決:
     

        private  static  object GetMemberExpressionValue(MemberExpression member)
        {
             var objectMember = Expression.Convert(member,  typeof( object));
             var getterLambda = Expression.Lambda<Func< object>>(objectMember);
             var getter = getterLambda.Compile();
             return getter();
        }
        


        這個方法的思路就是將MemberExpression轉換成可執行的Lambda表達式,通過執行Lambda表達式得到計算值。
    
     有了上面的方法,我們需要修改下原來的解析方法:主要是判斷參數表達式的類型,如果是值類型就走以前的邏輯,如果是引用類型,我們需要從MemberExpression中抽取數值,有了數據我們的問題就解決了。
                    

                   case ExpressionType.Call:
                    {
                         var be = (MethodCallExpression)node;
                         var resultValue =  string.Empty;
                         switch (be.Arguments[ 0].NodeType)
                        {
                             case ExpressionType.MemberAccess:
                                 var exprssion = GetMemberExpressionValue((MemberExpression)be.Arguments[ 0]);
                                 if ( null != exprssion)
                                {
                                    resultValue = exprssion.ToString();
                                }
                                 break;
                             case ExpressionType.Constant:
                                resultValue=((ConstantExpression)be.Arguments[ 0]).Value.ToString();
                                 break;
                        }
                         var expression = GetMethodExpression((MemberExpression)be.Object, resultValue, be.Method.Name, subst);
                         return expression.Body;
                    }

                    
      到此,表達式樹的轉換終於可以在實際項目應用了,這對我們動態搜集查詢條件非常有幫助,盡管這個問題困擾我超過半個月時間,但最終還是得到解決了。表達式的轉換比較麻煩,如果想讓自己的表達式轉換功能越來越強大,那么我們需要針對不同的情況編寫對應的解決方案才行,沒有完美只有最適合。
 


免責聲明!

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



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