ASP.NET MVC: jqGrid и Dynamic LINQ и Expressions.

Продолжаю эксперименты с jqGrid. На этот раз дотянулись руки до создания динамического запроса.

В прошлой статье удалось сократить сигнатуру метода получения данных ProductData. Но остался большой кусок кода отвечающий за создание запроса на основании введенных пользователем данных в поля фильтра таблицы. Все бы ничего, но кодить такое совершенно не доставляет удовольствия.

Для начала необходимо скачать исходники проекта. Фактически это итоговый проект того что было сделано в прошлой статье.

Откроем ProductController.cs, и внимательнее приглядимся к методу ProductData, а точнее к той части где накладываются фильтры на коллекцию:

 	
            if (_search)
            {
                if (null != searchProduct.ProductId)
                    list = list.Where(x => searchProduct.ProductId == x.ProductId);

                if (!String.IsNullOrEmpty(searchProduct.ProductName))
                    list = list.Where(x => x.ProductName == searchProduct.ProductName);

                if (!String.IsNullOrEmpty(searchProduct.Category))
                    list = list.Where(x => x.Category == searchProduct.Category);

                if (!String.IsNullOrEmpty(searchProduct.Supplier))
                    list = list.Where(x => x.Supplier == searchProduct.Supplier);

                if (null != searchProduct.UnitPrice)
                    list = list.Where(x => x.UnitPrice == searchProduct.UnitPrice);

                if (null != searchProduct.UnitsInStock)
                    list = list.Where(x => x.UnitsInStock == searchProduct.UnitsInStock);

                if (!String.IsNullOrEmpty(searchProduct.EnglishName))
                    list = list.Where(x => x.EnglishName == searchProduct.EnglishName);
            }

Первое что видно что идет поиск свойств у которых есть значения. В этом нам поможет Reflection. Результатом работы следующего кода является список свойств с значениями.

 
            var props = filterValues.GetType().GetProperties()
                        .Where(propertyInfo => 
                                     propertyInfo.GetValue(filterValues, null) != null)
                        .ToList();

Теперь у нас есть список свойств, используя который можно без особого труда создать лямбда выражения и отфильтровать список. В исходное лямбда-выражение выглядят следующим образом:

 	
x => x.ProductName == searchProduct.ProductName

Где x — аргумент, свойства которого используются в выражении,
searchProduct.ProductName — значение для правой части условия.
x.ProductName — свойство объекта, используемое в условии.
Expression<Func> — тип этого лямбда-выражения.
В итоге, для получения лямбда-выражения понадобиться имя свойства, значение фильтра и тип объекта.
Собственно:

        public static Expression<Func<T, bool>> CreatePredicate<T>(string propertyName, object propertyValue)
        {
            var argument = Expression.Parameter(typeof (T),"x");
            var property = Expression.Convert(
                                               Expression.Property(argument, propertyName),
                                               propertyValue.GetType());
            var value = Expression.Constant(propertyValue);
            var body = Expression.Equal(property, value);

            return Expression.Lambda<Func<T, bool>>(body,argument);
        }

Стоит пояснить вот эту строчку:

 
var property = Expression.Convert(
                      Expression.Property(argument, propertyName), 
                      propertyValue.GetType()); 

Если тип исходного свойства класса не привести к типу аргумента, то обязательно возникнет исключение:
System.InvalidOperationException: Двоичный оператор Equal не определен для типов «System.Nullable`1[System.Decimal]» и «System.Decimal».

На основе этого я создал метод расширения WhereFilter для IQueryable. Итоговые исходники проекта можно скачать тут.