Расширение IQueryable: создайте лямбда-выражение для запроса столбца для ключевого слова - PullRequest
1 голос
/ 13 сентября 2010

Я начал с методов расширения IQueryable из этого примера на CodePlex .

Мне нужен метод расширения IQueryable для «Где», где сигнатура метода выглядит следующим образом:

public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)

и эффективно делает это (при условии, что T.columnName имеет тип string):

source.Where(p => p.ColumnName.Contains("keyword"))

с использованием приведенного выше примера CodePlex, я думаю, я понимаю, как он получил метод OrderBy, работающий, но моя проблема кажется немного более сложной, и я не знаю, как заставить работать часть Contains ("ключевое слово").

Заранее спасибо,

- Ed

Обновление: 13.09.2010 18:26 PST

Я думал, что следующее сработает, но в итоге получит NotSupportedException (тип узла выражения LINQ 'Invoke' не поддерживается в LINQ to Entities.), когда я выполняю выражение через Count ().Есть идеи?

    public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)
    {
        var type = typeof(T);
        var property = type.GetProperty(columnName);
        if (property.PropertyType == typeof(string))
        {
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var sel = Expression.Lambda<Func<T, string>>(propertyAccess, parameter);
            var compiledSel = sel.Compile();
            return source.Where(item => compiledSel(item).Contains(keyword));
        }
        else
        {
            return source;
        }
    }

Ответы [ 4 ]

9 голосов
/ 13 сентября 2010
public static IQueryable<T> Where<T>(
    this IQueryable<T> source, string columnName, string keyword)
{
    var arg = Expression.Parameter(typeof(T), "p");

    var body = Expression.Call(
        Expression.Property(arg, columnName),
        "Contains",
        null,
        Expression.Constant(keyword));

    var predicate = Expression.Lambda<Func<T, bool>>(body, arg);

    return source.Where(predicate);
}
6 голосов
/ 23 марта 2012

Ну, пару лет спустя, но если кому-то это все еще нужно, вот оно:

    public static IQueryable<T> Has<T>(this IQueryable<T> source, string propertyName, string keyword)
    {
        if (source == null || propertyName.IsNull() || keyword.IsNull())
        {
            return source;
        }
        keyword = keyword.ToLower();

        var parameter = Expression.Parameter(source.ElementType, String.Empty);
        var property = Expression.Property(parameter, propertyName);

        var CONTAINS_METHOD = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        var TO_LOWER_METHOD = typeof(string).GetMethod("ToLower", new Type[] { });

        var toLowerExpression = Expression.Call(property, TO_LOWER_METHOD);
        var termConstant = Expression.Constant(keyword, typeof(string));
        var containsExpression = Expression.Call(toLowerExpression, CONTAINS_METHOD, termConstant);

        var predicate = Expression.Lambda<Func<T, bool>>(containsExpression, parameter);

        var methodCallExpression = Expression.Call(typeof(Queryable), "Where",
                                    new Type[] { source.ElementType },
                                    source.Expression, Expression.Quote(predicate));

        return source.Provider.CreateQuery<T>(methodCallExpression);
    }

Я назвал свой метод «Имею», просто чтобы он был коротким, пример использования:

filtered = filtered.AsQueryable().Has("City", strCity)

И вы можете объединить, чтобы сделать его еще более выразительным:

filtered = filtered.AsQueryable().Has("Name", strName).Has("City", strCity).Has("State", strState);

Кстати, "IsNull ()", прикрепленный к строкам, это просто еще один простой метод расширения:

    public static Boolean IsNull(this string str)
    {
        return string.IsNullOrEmpty(str);
    }
1 голос
/ 13 сентября 2010

Часть .Contains("keyword") в вашем примере совершенно правильная.

Это часть p.ColumnName, которая может вызвать проблемы.

Теперь есть несколько способов сделать это.обычно включает либо отражение, либо Expression<>, ни один из которых не особенно эффективен.

Проблема здесь в том, что, передавая имя столбца в виде строки, вы пытаетесь отменить то, что было изобретено LINQ.

Однако, возможно, есть и более эффективные способы выполнения вашей общей задачи, кроме этого.

Итак, давайте рассмотрим альтернативные способы:

Вы хотите сказать:

   var selector = new Selector("Column1", "keyword");
   mylist.Where(item => selector(item));

, и это было эквивалентно

    mylist.Where(item=> item.Column1.Contains("keyword"));

Как насчет:

   Func<MyClass, string> selector = i => i.Column1;
   mylist.Where(item => selector(item).Contains("keyword"));

или

   Func<MyClass, bool> selector = i => i.Column1.Contains("keyword");
   mylist.Where(item => selector(item));

Они легко расширяются для альтернатив:

   Func<MyClass, string> selector;
   if (option == 1)
        selector = i => i.Column1;
   else
        selector = i => i.Column2;
   mylist.Where(item => selector(item).Contains("keyword"));
0 голосов
/ 13 сентября 2010

Видите, в обобщенном типе объект работает динамически. Поэтому, когда p.ColumnName берется в качестве строки, выполняется содержимое строки.

Как правило, для любого лямбда-выражения, которое вы укажете, оно разбирает вещь в Expression и, в конечном счете, выдает результат во время выполнения.

Если вы видите мой пост: http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html вы поймете, как это работает на самом деле.

...