Изменение лямбда-выражения

Я разрабатываю приложение с NHibernate 3.0. Я разработал шляпу репозитория, принимающую выражение, чтобы сделать некоторый фильтр с помощью QueryOver. Мой метод примерно такой:

public IEnumerable<T> FindAll(Expression<Func<T, bool>> filter) {
   return Session.QueryOver<T>().Where(filter).List();
}

Это работает нормально. Итак, у меня также есть уровень службы, и мои методы в этих службах принимают типы примитивов, например:

public IEnumerable<Product> GetProducts(string name, int? stock, int? reserved) {

  // how init the expression ?    
  Expression<Func<Product, bool>> expression = ???;

  if (!string.IsNullOrEmpty(name)) {
     //add AND condition for name field in expression
  }  
  if (stock.HasValue) {
     //add AND condition for stock field in expression
  }
  if (reserved.HasValue) {
     //add AND condition for reserved field in expression
  }

  return _repository.FindAll(expression);
}

Мои сомнения:

Является ли это возможным ? При необходимости добавить некоторые условия (когда мои параметры имеют значение)?

Спасибо

/// мои правки

public ActionResult Index(ProductFilter filter) {
   if (!string.IsNullOrEmpty(filter.Name) {
      return View(_service.GetProductsByName(filter.Name))
   }

   // others  conditions
}

/// Почти решение

Expression<Func<Product, bool>> filter = x => true;

if (!string.IsNullOrEmpty(name))
    filter = x => filter.Compile().Invoke(x) && x.Name == name;

if (stock.HasValue) 
    filter = x => filter.Compile().Invoke(x) && x.Stock == stock.Value;

if (reserved.HasValue)
    filter = x => filter.Compile().Invoke(x) && x.Reserved == reserved.Value;

return _repository.FindAll(filter);

person Felipe Oriani    schedule 20.01.2011    source источник
comment
Я знаю, как вы можете это сделать, но мне потребуется некоторое время, чтобы реализовать его. но вы могли бы просто сделать x=>(!string.IsNullOrEmpty(name)||name==x.Name)&&(stock.HasValue||x.Stock==stock??0) ... etc Что я мог бы добавить для вас, так это способ заменить закрытие константами, а затем сократить логическую оценку, чтобы выражение получилось настолько коротким, насколько вы хотите.   -  person Neil    schedule 20.01.2011
comment
Привет, Нил. Если бы это было возможно, я хотел бы увидеть код, если вы можете мне помочь = D. Я представил себе это так, как вы сказали, и я изучу эту возможность. Спасибо!   -  person Felipe Oriani    schedule 21.01.2011
comment
@Felipe Смотрите мой ответ - вот и все.   -  person Neil    schedule 21.01.2011
comment
Хорошо, я думаю, у вас возникнут проблемы с предложенным вами решением. Проблема в том, что как только вы вызовете Compile() и Invoke(), вы потеряете метаданные, на которые nhibernate полагается при построении запроса. По сути, вы получите то, что можно оценить только на клиенте, а не то, что можно будет преобразовать в SQL.   -  person Neil    schedule 21.01.2011
comment
Спасибо, Нил, я выберу твое решение. Спасибо =D   -  person Felipe Oriani    schedule 21.01.2011
comment
Ну, теперь у вас есть 2 на выбор :)   -  person Neil    schedule 21.01.2011


Ответы (4)


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

class MyClass
{
     public string Name { get; set; }
     public bool Hero { get; set; }
     public int Age { get; set; }
}

И мы хотим запросить его следующим образом:

   string name = null;
   int? age = 18;
   Expression<Func<MyClass, bool>> myExpr = 
      x => (string.IsNullOrEmpty(name) || x.Name == name) && 
           (!age.HasValue || x.Age > (age ?? 0));
   myExpr = myExpr.RemoveCloture(); // this line here - removes the cloture - 
               // and replaces it with constant values - and shortcuts 
               // boolean evaluations that are no longer necessary.
               // in effect this expression now becomes :
               // x => x.Age > 18
   bool result = myExpr.Compile()(
      new MyClass {Name = "Rondon", Hero = true, Age = 92});

Так что все, что вам нужно сделать, это написать RemoveCloture(); — не проблема.

// using System;
// using System.Linq.Expressions;

public static class ClotureRemover
{

#region Public Methods

public static Expression<TExpressionType> RemoveCloture<TExpressionType>(
    this Expression<TExpressionType> e)
{
    var converter = new RemoveClotureVisitor();
    var newBody = converter.Visit(e.Body);
    return Expression.Lambda<TExpressionType>(newBody, e.Parameters);
}

#endregion

private class RemoveClotureVisitor : ExpressionVisitor
{


    public RemoveClotureVisitor()
    {
    }


    public override Expression Visit(Expression node)
    {
        if (!RequiresParameterVisitor.RequiresParameter(node))
        {
            Expression<Func<object>> funct = () => new object();
            funct = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object)), funct.Parameters);
            object res = funct.Compile()();
            return ConstantExpression.Constant(res, node.Type);
        }
        return base.Visit(node);
    }


    protected override Expression VisitBinary(BinaryExpression node)
    {
        if ((node.NodeType == ExpressionType.AndAlso) || (node.NodeType == ExpressionType.OrElse))
        {
            Expression newLeft = Visit(node.Left);
            Expression newRight = Visit(node.Right);

            bool isOr = (node.NodeType == ExpressionType.OrElse);
            bool value;
            if (IsBoolConst(newLeft, out value))
            {
                if (value ^ isOr)
                {
                    return newRight;
                }
                else
                {
                    return newLeft;
                }
            }

            if (IsBoolConst(newRight, out value))
            {
                if (value ^ isOr)
                {
                    return newLeft;
                }
                else
                {
                    return newRight;
                }
            }
        }
        return base.VisitBinary(node);
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked)
        {
            Expression newOpperand = Visit(node.Operand);
            if (newOpperand.Type == node.Type)
            {
                return newOpperand;
            }
        }
        return base.VisitUnary(node);
    }

    private static bool IsBoolConst(Expression node, out bool value)
    {
        ConstantExpression asConst = node as ConstantExpression;
        if (asConst != null)
        {
            if (asConst.Type == typeof(bool))
            {
                value = (bool)asConst.Value;
                return true;
            }
        }
        value = false;
        return false;
    }
}

private class RequiresParameterVisitor : ExpressionVisitor
{
    protected RequiresParameterVisitor()
    {
        result = false;
    }

    public static bool RequiresParameter(Expression node)
    {
        RequiresParameterVisitor visitor = new RequiresParameterVisitor();
        visitor.Visit(node);
        return visitor.result;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        result = true;
        return base.VisitParameter(node);
    }

    internal bool result;
}

}

person Neil    schedule 21.01.2011
comment
Привет, Нил, очень хорошее решение, я проверю со своей командой возможность сделать это. Но я хотел бы узнать ваше мнение о решении, которое я пишу в своем первом посте, посмотрите и выскажите свое мнение, хорошее ли это решение?! Спасибо чувак! - person Felipe Oriani; 21.01.2011
comment
Мой комментарий прикреплен к вашему вопросу. - person Neil; 21.01.2011

Во-первых, я бы решил вашу проблему, избегая ее в первую очередь. У меня были бы разные методы для этого.

public IEnumerable<Product> GetProductsByName(string name)
public IEnumerable<Product> GetProudctsByNameAndStock(string name, int stock)
public IEnumerable<Product> GetProductsByNameAndReserved(
    string name,
    int reserved
)
public IEnumerable<Product> GetProducts(string name, int stock, int reserved)

Все они имеют тривиально простые реализации с точки зрения лямбда-выражения. Например:

public IEnumerable<Product> GetProductsByName(string name) {
    return GetProductsByExpression(p => p.Name == name);
}

private IEnumerable<Product> GetProductsByExpression(
    Expression<Func<Product, bool>> expression
) {
    return _repository.FindAll(expression);
}

и Т. Д.

Является ли это возможным ? При необходимости добавить некоторые условия (когда мои параметры имеют значение)?

Во-вторых, да, то, что вы хотите сделать, возможно, но я бы не решил проблему так.

person jason    schedule 20.01.2011
comment
Привет, Джейсон, спасибо, но в моей презентации, когда мне нужно вызвать службу, нужно ли мне проверять каждый параметр? (смотрите мои правки в первом посте). Я хотел бы иметь один метод и передавать параметры, и это ... У меня есть некоторые моменты, когда у меня будет 10 параметров для фильтра, и их можно комбинировать ... для этого я спрашиваю, возможно ли сделать динамическое выражение... Спасибо! - person Felipe Oriani; 20.01.2011

Ваше определение метода репозитория предполагает, что вы рассматриваете FindAll как нечто, что вы передаете критериям и возвращаете завершенный результат. Почему бы вместо этого просто не получить результат типа IQueryable и не вернуть Session.QueryOver?

Затем ваш сервисный уровень сделает что-то вроде этого, объединив «где»:


var query = _repository.FindAll();
if (!string.IsNullOrEmpty(name))
  query = query.Where(x => x.Name == name);
if (stock.HasValue)
  query = query.Where(x => x.Stock == stock);
etc...

return query.ToList();

person Rich    schedule 20.01.2011
comment
Это тоже хорошее решение, но я предпочитаю оставить эту ответственность за свой репозиторий. - person Felipe Oriani; 21.01.2011

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

public static class AddExpressions
{
   public static Expression<Func<TFrom, TTo>> AndLambdas<TFrom, TTo>(this Expression<Func<TFrom, TTo>> first, Expression<Func<TFrom, TTo>> second)
   {    
     ParameterExpression paramToUse = first.Parameters[0];
     Expression bodyLeft = first.Body;
     ConversionVisitor visitor = new ConversionVisitor(paramToUse, second.Parameters[0]);
     Expression bodyRight = visitor.Visit(second.Body);
     return Expression.Lambda<Func<TFrom, TTo>>(Expression.MakeBinary(ExpressionType.AndAlso, bodyLeft, bodyRight), first.Parameters);
   }

class ConversionVisitor : ExpressionVisitor
{
    private readonly ParameterExpression newParameter;
    private readonly ParameterExpression oldParameter;

    public ConversionVisitor(ParameterExpression newParameter, ParameterExpression oldParameter)
    {
        this.newParameter = newParameter;
        this.oldParameter = oldParameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return newParameter; // replace all old param references with new ones
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression != oldParameter) // if instance is not old parameter - do nothing
            return base.VisitMember(node);

        var newObj = Visit(node.Expression);
        var newMember = newParameter.Type.GetMember(node.Member.Name).First();
        return Expression.MakeMemberAccess(newObj, newMember);
    }
}

}

Тогда вызвать код достаточно просто....

    class MyClass
    {
        public string Name { get; set; }
        public bool Hero { get; set; }
        public int Age { get; set; }

    }

...

 Expression<Func<MyClass, bool>> expression1 = x => x.Age > (age ?? 0);
 Expression<Func<MyClass, bool>> expression2 = x => x.Name == name;

 expression1 = expression1.AndLambdas(expression2);
 result = expression1.Compile()(new MyClass { 
            Name = "Rondon", 
            Hero = true, 
            Age = 92 });
person Neil    schedule 21.01.2011