Есть ли простой способ добавить лямбда-выражения и повторно использовать лямбда-имя для создания условия Linq where? - PullRequest
4 голосов
/ 12 мая 2009

У меня есть пользовательский элемент управления, который принимает Func, который затем передает методу расширения Linq "Where" IQueryable. Идея состоит в том, что из вызывающего кода я могу передать нужную функцию поиска.

Я бы хотел динамически построить эту функцию поиска:

Func<Order, bool> func == a => true;
if (txtName.Text.Length > 0) {
  //add it to the function
  func = a => func(a) && a.Name.StartsWith(txtName.Text);
}
if (txtType.Text.Length > 0) {
  //add it to the function
  func = a => func(a) && a.Type == txtType.Text;
}
..... etc .....

Проблема с этим подходом состоит в том, что, поскольку я повторно использую имя "func", он создает рекурсивную функцию.

Есть ли простой способ построить дерево выражений, подобное этому, для создания динамического предложения where (при отсутствии предварительного запроса IQueryable и повторного вызова «где»)?

Ответы [ 5 ]

6 голосов
/ 12 мая 2009

Просто сохраните текущую лямбду во временной переменной, чтобы предотвратить рекурсию.

var tempFunc = func;
func = a => tempFunc(a) && ...
1 голос
/ 12 мая 2009

Если вы хотите создать комбинацию «и», предпочтительным вариантом является использование нескольких предложений «где»:

IQueryable<Order> query = ...
if (!string.IsNullOrEmpty(txtName.Text.Length)) {
  //add it to the function
  query = query.Where(a => a.Name.StartsWith(txtName.Text));
}
if (!string.IsNullOrEmpty(txtType.Text.Length)) {
  //add it to the function
  query = query.Where(a => a.Type == txtType.Text);
}

Вы можете делать более сложные вещи с помощью построения выражений (AndAlso, Invoke и т. Д.), Но это не обязательно для комбинации "и".

Если вам действительно нужно объединить выражения, то подход зависит от реализации. LINQ-to-SQL и LINQ-to-Objects поддерживают Expression.Invoke, что позволяет:

static Expression<Func<T, bool>> OrElse<T>(
    this Expression<Func<T, bool>> lhs,
    Expression<Func<T, bool>> rhs)
{
    var row = Expression.Parameter(typeof(T), "row");
    var body = Expression.OrElse(
        Expression.Invoke(lhs, row),
        Expression.Invoke(rhs, row));
    return Expression.Lambda<Func<T, bool>>(body, row);
}
static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> lhs,
    Expression<Func<T, bool>> rhs)
{
    var row = Expression.Parameter(typeof(T), "row");
    var body = Expression.AndAlso(
        Expression.Invoke(lhs, row),
        Expression.Invoke(rhs, row));
    return Expression.Lambda<Func<T, bool>>(body, row);
}

Однако для Entity Framework вам обычно нужно разорвать Expression на части и перестроить его, что не просто. Следовательно, почему часто предпочтительнее использовать Queryable.Where (для "и") и Queryable.Concat (для "или").

0 голосов
/ 12 мая 2009
Dictionary<Func<bool>, Expression<Func<Order, bool>>> filters =
  new Dictionary<Func<bool>, Expression<Func<Order, bool>>>();
// add a name filter
filters.Add(
  () => txtName.Text.Length > 0,
  a => a.Name.StartsWith(txtName.Text)
);
// add a type filter
filters.Add(
  () => txtType.Text.Length > 0,
  a => a.Type == txtType.Text
);

...

var query = dc.Orders.AsQueryable();

foreach( var filter in filters
  .Where(kvp => kvp.Key())
  .Select(kvp => kvp.Value) )
{
  var inScopeFilter = filter;
  query = query.Where(inScopeFilter);
}
0 голосов
/ 12 мая 2009

Я нахожусь в процессе выполнения именно этого ... Я использую выражения, потому что Func - это скомпилированный код, где Expression<Func<YourObect, boo>> может быть преобразован в C # или TSql или что-то еще ... Я только что видел, как несколько человек рекомендуют использовать выражение вместо просто func.

На странице поиска вы реализуете код, подобный следующему:

SearchCritera<Customer> crit = new SearchCriteria<Customer>();

if (txtName.Text.Length > 0) {
  //add it to the function
  crit.Add(a.Name.StartsWith(txtName.Text));
}

if (txtType.Text.Length > 0) {
  //add it to the function
  crit.Add(a.Type == txtType.Text));
}

Объект SearchCriteria выглядит примерно так ...

public class SearchCritera<TEntity>
    {
        private List<Expression<Func<TEntity, bool>>> _Criteria = new List<Expression<Func<TEntity, bool>>>();

        public void Add(Expression<Func<TEntity, bool>> predicate)
        {
            _Criteria.Add(predicate);
        }

        // This is where your list of Expression get built into a single Expression 
        // to use in your Where clause
        public Expression<Func<TEntity, bool>> BuildWhereExpression()
        {
            Expression<Func<TEntity, bool>> result = default(Expression<Func<TEntity, bool>>);
            ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "entity");
            Expression previous = _Criteria[0];

            for (int i = 1; i < _Criteria.Count; i++)
            {
                previous = Expression.And(previous, _Criteria[i]);
            }

            result = Expression.Lambda<Func<TEntity, bool>>(previous, parameter);

            return result;
        }
    }

Тогда из пункта «Где» вы можете сделать это ...

public List<Customer> FindAllCustomers(SearchCriteria criteria)
{
   return LinqToSqlDataContext.Customers.Where(SearchCriteria.BuildWhereExpression()).ToList();
}

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

0 голосов
/ 12 мая 2009

Если вы собираетесь использовать это в LinqToSQL или любом другом анализаторе дерева динамических выражений, вам нужно будет использовать PredicateBuilder !!!

В противном случае:

Этот метод расширения предотвратит рекурсию:

    public static Func<T, bool> And<T>(this Func<T, bool> f1, Func<T, bool> f2)
    {
        return a => f1(a) && f2(a);
    }

    public static Func<T, bool> Or<T>(this Func<T, bool> f1, Func<T, bool> f2)
    {
        return a => f1(a) || f2(a);
    }

Используйте это так:

Func<Order, bool> func == a => true;
if (txtName.Text.Length > 0) {
  //add it to the function
  func.And(a => a.Name.StartsWith(txtName.Text));
}
if (txtType.Text.Length > 0) {
  //add it to the function
  func.And(a => a.Type == txtType.Text);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...