Объединение выражений в дереве выражений - PullRequest
6 голосов
/ 10 августа 2010

Как я могу построить дерево выражений, когда части выражения передаются в качестве аргументов?

Например, что если бы я хотел создать деревья выражений, подобные этим:

IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar)
{
  query=query.Where(x => x.Foo.StartsWith(foo));
  return query.Where(x => x.Bar.StartsWith(bar));
}

, но путем созданиякосвенно:

IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar)
{
  query=testAdd(query, x => x.Foo, foo);
  return testAdd(query, x => x.Bar, bar);
}

IQueryable<T> testAdd<T>(IQueryable<T> query, 
  Expression<Func<T, string>> select, string find)
{
  // how can I combine the select expression with StartsWith?
  return query.Where(x => select(x) .. y => y.StartsWith(find));
}

Результат:

Хотя сэмплы не имели особого смысла (извините, но я старался сделать это простым), вот результат(спасибо Quartermeister).

Может использоваться с Linq-to-Sql для поиска строки, начинающейся с или равной findText.

public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query, 
  Expression<Func<T, string>> selectField, string findText)
{
  Expression<Func<string, bool>> find;
  if (string.IsNullOrEmpty(findText) || findText=="*") return query;

  if (findText.EndsWith("*")) 
    find=x => x.StartsWith(findText.Substring(0, findText.Length-1));
  else
    find=x => x==findText;

  var p=Expression.Parameter(typeof(T), null);
  var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p));

  return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p));
}

например

var query=context.User;

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName);
query=WhereLikeOrExact(query, x => x.LastName, find.LastName);

Ответы [ 3 ]

5 голосов
/ 10 августа 2010

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

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find)
{
    Expression<Func<string, bool>> startsWith = y => y.StartsWith(find);
    var parameter = Expression.Parameter(typeof(T), null);
    return query.Where(
        Expression.Lambda<Func<T, bool>>(
            Expression.Invoke(
                startsWith,
                Expression.Invoke(select, parameter)),
            parameter));
}

Внутренний Expression.Invoke представляет выражение select(x), а внешний представляет вызов y => y.StartsWith(find) для значения, возвращаемого select(x).

Вы также можете использовать Expression.Call для представления вызова StartsWith без использования второй лямбды:

IQueryable<T> testAdd<T>(IQueryable<T> query,
    Expression<Func<T, string>> select, string find)
{
    var parameter = Expression.Parameter(typeof(T), null);
    return query.Where(
        Expression.Lambda<Func<T, bool>>(
            Expression.Call(
                Expression.Invoke(select, parameter),
                "StartsWith",
                null,
                Expression.Constant(find)),
            parameter));
}
3 голосов
/ 10 августа 2010

Это работает:

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1,
                    Expression<Func<T, string>> Selector2, string data1, string data2)
{
    return Add(Add(query, Selector1, data1), Selector2, data2);
}

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data)
{
    var row = Expression.Parameter(typeof(T), "row");
    var expression =
        Expression.Call(
            Expression.Invoke(Selector, row),
            "StartsWith", null, Expression.Constant(data, typeof(string))
        );
    var lambda = Expression.Lambda<Func<T, bool>>(expression, row);
    return query.Where(lambda);
}

Вы используете его как:

IQueryable<XlUser> query = SomehowInitializeIt();
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar");
1 голос
/ 10 августа 2010

Обычно вы делаете это не так, как вы описали (используя интерфейс IQueryable), а скорее используете выражения типа Expression<Func<TResult, T>>.Сказав это, вы объединяете функции более высокого порядка (например, where или select) в запрос и передаете выражения, которые "заполнят" желаемую функциональность.

Например, рассмотрите сигнатуруметод Enumerable.Where:

Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)

Функция принимает делегата в качестве второго аргумента, который вызывается для каждого элемента.Значение, которое вы возвращаете из этого делегата, указывает функции более высокого порядка, будет ли она давать текущий элемент (включить его в результат или нет).

Теперь давайте взглянем на Queryable.Where:

Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>)

Мы можем наблюдать тот же шаблон функции более высокого порядка, но вместо делегата Func<> требуется выражение.Выражение - это в основном представление данных вашего кода.Компиляция этого выражения даст вам реальный (исполняемый) делегат.Компилятор выполняет тяжелую работу по созданию деревьев выражений из лямбд, которые вы назначаете для Expression<...>.Деревья выражений позволяют скомпилировать описанный код для разных источников данных, таких как база данных SQL Server.

Чтобы вернуться к вашему примеру, я думаю, что вы ищете селектор .Селектор берет каждый элемент ввода и возвращает его проекцию.Это подпись выглядит так: Expression<Func<TResult, T>>.Например, вы можете указать это:

Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string

Чтобы передать селектор, ваш код должен выглядеть следующим образом:

IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find)
{
  // how can I combine the select expression with StartsWith?
  return query.Select(selector) // IQueryable<string> now
              .Where(x => x.StartsWith(find));
}

Этот селектор позволит вам спроецировать вводСтрока до нужного типа.Надеюсь, я правильно понял ваше намерение, трудно понять, чего вы пытаетесь достичь.

...