[Expression Tree] LINQ動態欄位查詢

   最近專案有一需求是讓使用者挑選所需的欄位來顯示在列表上,以往用SQL語法都是組字串去查詢,但現在用的是Entityframework,若要在LINQ的查詢上作這件事,似乎就沒那麼容易....,幸好前陣子有對Expression作一些學習,將舊文章再溫習一篇後將此功能實作出來。


動態查詢欄位

如下程式,相關註解已寫在每行程式上方,唯一較特別的是此方法傳入的IQueryable是ProductModel但回傳的是ProductTargetModel,這是因為在Linq To Entities的查詢預設不支援同一型別的回傳。

 private static IQueryable<ProductTargetModel> SelectDynamicColumns(IQueryable<ProductModel> source, params string[] columns)
        {
            //傳入參數 如:m
            ParameterExpression parSource = Expression.Parameter(source.ElementType, "m");
            Type targetType = typeof(ProductTargetModel);
            //目標型別的建構式
            var ctor = Expression.New(targetType);
            List<MemberAssignment> assignment = new List<MemberAssignment>();
            foreach (var column in columns)
            {
                //來源屬性
                var sourceValueProperty = source.ElementType.GetProperty(column);
                //目標屬性
                var targetValueProperty = targetType.GetProperty(column);
                //建立參數與欄位節點 如:m.CategoryName
                Expression columnExp = Expression.Property(parSource, sourceValueProperty);
                //建立成員指定節點 如:CategoryName=m.CategoryName
                var displayValueAssignment = Expression.Bind(targetValueProperty, columnExp);
                assignment.Add(displayValueAssignment);
            }
            //將目標型別的成員初始化 例:new ProductTargetModel(){CategoryName=m.CategoryName,...}
            var memberInit = Expression.MemberInit(ctor, assignment.ToArray());

            //建立Lamba運算式 例: m => new ProductTargetModel(){CategoryName=m.CategoryName,...}
            Expression body = Expression.Lambda<Func<ProductModel, ProductTargetModel>>(memberInit, new ParameterExpression[] { parSource });
            //呼叫Select方法
            MethodCallExpression whereCallExpression = Expression.Call(
                            typeof(Queryable),//來源型別
                            "Select",//指定方法名稱
                            new Type[] { typeof(ProductModel), typeof(ProductTargetModel) },//body lamba使用到Expression型別,例:pe及回傳型別
                            source.Expression,//來源運算式
                            body);
            var result = (IQueryable<ProductTargetModel>)source.Provider.CreateQuery(whereCallExpression);
            return result;
        }

測試程式

 static void Main(string[] args)
        {
            //DynamicQuery();
            DynamicColumn();
            Console.Read();
        }
 

        private static void DynamicColumn()
        {
            IQueryable<ProductModel> data = ProductModel.GetDatas();
            var dynamicResult = SelectDynamicColumns(data, "ProductName", "CategoryName");
            foreach (var item in dynamicResult)
            {
                Console.WriteLine(item.ProductName + "-" + item.CategoryName);
            }
        }

參考來源


http://stackoverflow.com/questions/12701737/expression-to-create-an-instance-with-object-initializer
 

範例程式


https://github.com/kimx/ExpressionLab

這個網誌中的熱門文章

[TFS] 分支與合併

[.NET Core] 將專案發行至IIS