Linq to Entity經驗:表達式轉換


    最近一年的項目,我主要負責一些小型項目(就是指企業內部的小項目),在數據庫操作方面我們采用了以開發速度快為特點的Linq to Entity,這里我不多講它的使用方法,先來分享下我認為還不錯的幾個地方:
    
    1:某種情況下可以完全代替傳統SQL開發
      這里是指比較簡單的數據庫處理,比如查詢,幾個表關聯查詢及添加數據,更新數據,刪除數據等,不包含特別復雜的事務處理或者業務邏輯特別復雜的小型報表之類的處理,為此不需要員工一定會SQL語句的開發。
     
    2:性能上也是能接受的
      現在的Entity Framework5,微軟已經做出大量的優化工作,專門針對如何生成查詢更優的SQL語句做了大量努力,且一般小型項目也不會存在特別大的性能問題。大家發現問題了可以看下生成的SQL做特殊優化即可。
     
    3:簡化了開發過程
      一般情況下,我們首先需要將數據庫表數據加載成DataReader或者是DataTable,然后再轉換成我們的業務數據Model,有了EntityFrame work,它已經自動完成了此部分的轉換。
     
    問題:

           如何處理數據庫Model以及業務數據Model表達式之間的轉換?
    
    什么是數據庫Model?
   
          當我們采用數據庫優先方式創建了一個edmx文件后,它會生成和數據庫表名一樣的數據庫Model,比如我這里的DataAccess.ActionInfo
   
    什么是業務數據Model?


         真正的業務系統中,是不能直接使用系統自動生成的Model的,原因如下:
   
    1:這些代碼是生動生成的,隨時會發生改變,穩定性太差。
    2:數據庫Model完全和數據庫表對應,而業務數據Model有可能和數據庫模型不完全一致,或者完全不一樣。業務數據Model是處理業務邏輯的一個數據載體,由它解析成不同的數據庫Model,最近進行數據庫操作。比如我這里的ObjectModel.ActionInfo
   
    前幾篇文章中(我所理解的IRepository(續) ,我所理解的IRepository ),我提到我們項目中定義了如下倉儲接口用於數據庫查詢。
    

IList<T> QueryByPage<TKey>(Expression<Func<T,  bool>> filter, Expression<Func<T, TKey>> orderBy, int orderType,  int pageSize,  int pageIndex,  out  int recordsCount);

    
    它的條件接受一個表達式,由於UI屋以及業務邏輯層均只能調用業務數據Model,當進行數據庫查詢時,EntityFramwork只能接受參數類型為數據庫Model的對象,所以這里我們需要將類型為業務數據Model的表達式能夠自動的轉換成數據庫Model類型的表達式。
   
    比如我們在UI層可以這樣查詢數據,這里p的類型是業務數據Model
    

service.QueryByPage(p => (p.IsActive && p.FunctionInfoId == FunctionInfoId.Value), p => p.Id,  1, pageSize, pageIndex.Value +  1out recordCount);

    
    下面是我們的業務數據Model,包含一些MVC的數據驗證特性標簽,同時字段數量上也和表字段不相同。
    

View Code
public  class ActionInfo
    {
         #region Primitive Properties
        [Required(ErrorMessageResourceName= " ValidationMessageNameRequired ", ErrorMessageResourceType= typeof(ActionInfoResources))]
         public  virtual  string  Name
        {
             get;
             set;
        }         
        [Required(ErrorMessageResourceName= " ValidationMessageIsActiveRequired ", ErrorMessageResourceType= typeof(ActionInfoResources))]
         public  virtual  bool  IsActive
        {
             get;
             set;
        }   
         public  virtual  int  Id
        {
             get;
             set;
        }   
        [Required(ErrorMessageResourceName= " ValidationMessageFunctionInfoNameRequired ", ErrorMessageResourceType= typeof(ActionInfoResources))]
         public  virtual  int  FunctionInfoId
        {
             get;
             set;
        }   
        [Required(ErrorMessageResourceName= " ValidationMessageActionNameRequired ", ErrorMessageResourceType= typeof(ActionInfoResources))]
         public  virtual  string  ActionName
        {
             get;
             set;
        }           

         #endregion   
         #region Navigation Properties
         public  virtual FunctionInfo  FunctionInfo
        {
             get;
             set;
        }   
         #endregion
    }
    


      下面是生成的數據庫Model,代碼較多,這里就貼一個屬性內容,主要都是些和數據相關的內容,比如外鍵關系等:
    

[EdmScalarPropertyAttribute(EntityKeyProperty= false, IsNullable= false)]
        [DataMemberAttribute()]
         public  global::System.String Name
        {
             get
            {
                 return _Name;
            }
             set
            {
                OnNameChanging(value);
                ReportPropertyChanging( " Name ");
                _Name = StructuralObject.SetValidValue(value,  false);
                ReportPropertyChanged( " Name ");
                OnNameChanged();
            }
        }
        


        我們需要一個工具類能夠完成這兩種不同類型的表達式之間的類型轉換,需要滿足如下需求:
       
        1:最簡單的表達式條件
          對屬性做直接的條件比較,比如:等於,不等於,大於,小於,或 這里二元操作符。
          比如:p=>p.Name==1, p=>p.Name==1||p.Name==2
         
        2:需要支持部分函數
          如果我們想做某個字段的模糊查詢,在SQL中是用Like語句,在.net中我們可以調用StartWith,EndWith,Contains方法來完成
          比如:p=>p.Name.StartWith("1")
         
        解決思路:
        表達式樹一旦創建了它是不能修改其內容的,我們只能重新創建一個表達式樹,所以我們的工作就是解析源表達式樹,然后構造自己的表達式樹,在構建過程后也就完成類型的轉換。

        首先需要創建一個工具類用於轉換:

        TToB:指最終轉換成的類型,我們這里就直接理解為數據庫Model

        TR:就是表達式的返回值

        TFrom:指源對象類型

        核心就是調用ConvertNode方法進入表達式的解析。

       

  public  class ExpressionConverter<TToB>
    {
         public  static Expression<Func<TToB, TR>> Convert<TFrom, TR>(Expression<Func<TFrom, TR>> expr)
        {
            Dictionary<Expression, Expression> substitutues =  new Dictionary<Expression, Expression>();
             var oldParam = expr.Parameters[ 0];
             var newParam = Expression.Parameter( typeof(TToB), oldParam.Name);
            substitutues.Add(oldParam, newParam);
            Expression body = ConvertNode(expr.Body, substitutues);
             return Expression.Lambda<Func<TToB, TR>>(body, newParam);
        }
}


        需求1的解決方案:

            對於普通的條件,無非就是一些二元操作,所以我們只要了解下ExpressionType的內容,然后根據不同的類型做不同的解析即可,比如處理二元表達式:下面文章中列表出各種ExpressionType的詳細解釋: http://technet.microsoft.com/zh-cn/library/bb361179(en-us,VS.90).aspx

   

View Code
static Expression ConvertNode(Expression node, IDictionary<Expression, Expression> subst)
        {
             if (node ==  nullreturn  null;
             if (subst.ContainsKey(node))  return subst[node];

             switch (node.NodeType)
            {
                
                 case ExpressionType.Equal: 
                 case ExpressionType.And:
                 case ExpressionType.AndAlso:
                 case ExpressionType.LessThan:
                 case ExpressionType.NotEqual:
                 case ExpressionType.GreaterThan:
                 // case ExpressionType.:
                 case ExpressionType.Or:
                    {
                         var be = (BinaryExpression)node;
                         return Expression.MakeBinary(be.NodeType, ConvertNode(be.Left, subst), ConvertNode(be.Right, subst), be.IsLiftedToNull, be.Method);
                    }
                
                 default:
                     throw  new NotSupportedException(node.NodeType.ToString());
            }

        }
        


        需求2解決方案:

             對於這部分有函數調用的地方,我們需要做特殊處理,目前只處理常用的Contains,StartWith,EndWith,其它的函數也可以繼續做解析。
        

View Code
case ExpressionType.Call:
                    {
                         var be = (MethodCallExpression)node;

                         var expression = GetMethodExpression((MemberExpression)be.Object, ((ConstantExpression)be.Arguments[ 0]).Value.ToString(), be.Method.Name, subst);
                         return expression.Body;
                    }
        
         static Expression<Func<TToB,  bool>> GetMethodExpression(
            MemberExpression propertyExp, 
           
             string propertyValue, 
             string MethodName,
            IDictionary<Expression, Expression> subst)
        {
             // var parameterExp = Expression.Parameter(typeof(T), ((ParameterExpression)propertyExp.Expression).Name);
             var newParameterExp = (ParameterExpression)ConvertNode((ParameterExpression)propertyExp.Expression, subst);
          

             var newPropertyExp = ConvertNode(propertyExp, subst);
            MethodInfo method =  typeof( string).GetMethod(MethodName,  new[] {  typeof( string) });
             var someValue = Expression.Constant(propertyValue,  typeof( string));
             var containsMethodExp = Expression.Call(newPropertyExp, method, someValue);

             return Expression.Lambda<Func<TToB,  bool>>(containsMethodExp, newParameterExp);
        }

        
        補充:

            除了上面的二元表達式,方法調用表達式外,還有幾個表達式也是重點:
            1:ParameterExpression,即參數表達式,比如表達式p=>p.IsActive  這里的{p}就是一個參數表達式
            2:ConstantExpression,即常量表達式,比如表達式p=>p.Name==1 這里的1就是常量表達式
            3:MemberExpression,即成員表達式,比如p.Name,這一步的操作我們能夠實現業務數據Model與數據庫Model之間的類型轉換,它的原理就是比較成員是否一樣,這里包括屬性名稱,屬性類型等,有點類似反射,一個一個比,遇到相同的就賦值。
        
       總結:
             表達式樹雖然看起來不太容易使用,但只要明白它的一些基本用法就能解決你的大部分問題,重點就是需要了解如何解析表達式樹。
       
        


免責聲明!

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



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