使用Expression Tree產生Lambda

前言

因為久久要寫一個泛型的Expression Tree時,都需要找滿久的資料,以及花不少時間看懂微軟的範例程式,所以乾脆自己紀錄一下。

需求是這樣的,有一個GenericRepository的DAO,希望能夠有一個Select的功能。但是,因為這一個DAO是泛型的Repository,所以沒有辦法在Method中使用一般的Lambda來撰寫條件式。所以這個時候必須透過自己寫一個Expression Tree來表示條件式。

這是要搜尋的資料表轉換到程式碼的類別Product,現在希望能夠取得特定時段更新的商品資料。換句話說,就是利用Product_UpdateTime來篩選資料

public partial class Product
{
    public System.Guid Product_ID { get; set; }
    public string Product_Name { get; set; }
    public decimal Product_Price { get; set; }
    public System.DateTime Product_UpdateTime { get; set; }
}

如同前面所說的,需要創建一個Expression Tree來表示條件式。假設,我們希望能夠取出在2015/12/28這一天有更新的資料,先將平常使用的Lambda寫出來

p => p.Product_UpdateTime >= new DateTime(2015, 12, 28, 0, 0, 0) && p.Product_Update < new DateTime(2015, 12, 29, 0, 0, 0)

這就是我們想要的Lambda條件式,接下來就照著這一個條件式來寫Expression Tree。主要可以分為幾個步驟

  1. 創建p

    ParameterExpression pe = Expression.Parameter(typeof(T), "p");
    
  2. Product_UpdateTime >= or < DateTime

    //// p.Product_UpdateTime() >= new DateTime(2015, 12, 28, 0, 0, 0)
    Expression left = Expression.Property(pe, lastUpdateTimeName);
    Expression right = Expression.Constant(new DateTime(2015, 12, 28, 0, 0, 0), typeof(DateTime));
    Expression e1 = Expression.GreaterThanOrEqual(left, right);
    
    //// p.Product_UpdateTime < new DateTime(2015, 12, 29, 0, 0, 0)
    left = Expression.Property(pe, lastUpdateTimeName);
    right = Expression.Constant(new DateTime(2015, 12, 29, 0, 0, 0), typeof(DateTime));
    Expression e2 = Expression.LessThan(left, right);
    
  3. 兩個運算式組合,轉Lambda

    //// p.Product_UpdateTime() >= '2015/12/28' && p.Product_UpdateTime < '2015/12/29'
    Expression predicateBody = Expression.AndAlso(e1, e2);
    
    var predicate = Expression.Lambda<Func<T, bool>>(predicateBody, pe);
    

需要注意的地方應該只有,Expression right的部分,型別需要注意。另外還有一個需要特別注意的地方是e1的部分,因為e1是組合left, Right,所以需要注意這兩個參數型別是否有實作比較的方式,也就是GreaterThanOrEqual這一個method的部分。

底下是完整的method程式碼

    public class GenericRepository<T> : IRepository<T> where T : class, new()
    {
        private TestDBEntities _testDbContext;

        private IDbSet<T> _dbSet;

        public GenericRepository(TestDBEntities dbContext)
        {
            this._testDbContext = dbContext;
            this._dbSet = this._testDbContext.Set<T>();
        }

        public List<T> GetDataByUpdateTime(DateTime startDataTime, DateTime endDataTime)
        {
            var objs = new List<T>();
            var tName = typeof (T).Name;
            var lastUpdateTimeName = string.Format("{0}_UpdateTime", tName);
            // Compose the expression tree that represents the parameter to the predicate.
            ParameterExpression pe = Expression.Parameter(typeof(T), "p");

            // ***** Where(p => p.Product_UpdateTime() >= startDataTime && p.Product_UpdateTime < endDataTime) *****
            // Create an expression tree that represents the expression 'p.Product_UpdateTime() >= startDataTime'.
            Expression left = Expression.Property(pe, lastUpdateTimeName);
            Expression right = Expression.Constant(startDataTime, typeof(DateTime));
            Expression e1 = Expression.GreaterThanOrEqual(left, right);

            // Create an expression tree that represents the expression 'p.Product_UpdateTime < endDataTime'.
            left = Expression.Property(pe, lastUpdateTimeName);
            right = Expression.Constant(endDataTime, typeof(DateTime));
            Expression e2 = Expression.LessThan(left, right);

            // Combine the expression trees to create an expression tree that represents the
            // expression '(p.Product_UpdateTime() >= startDataTime && p.Product_UpdateTime < DendDataTime)'.
            Expression predicateBody = Expression.AndAlso(e1, e2);

            var predicate = Expression.Lambda<Func<T, bool>>(predicateBody, pe);
            objs = this._dbSet.Where(predicate).ToList();
            return objs;
        }
    }

參考資料:

How to: Use Expression Trees to Build Dynamic Queries (C# and Visual Basic) 

免責聲明:

"文章一定有好壞,文章內容有對有錯,使用前應詳閱公開說明書"