开发者

Modifying Lambda Expression

开发者 https://www.devze.com 2023-02-05 13:43 出处:网络
I\'m developing an application with NHibernate 3.0. I have developed a Repository hat accept a expression to do some filter with QueryOver. My method is something like this:

I'm developing an application with NHibernate 3.0. I have developed a Repository hat accept a expression to do some filter with QueryOver. My method is something like this:

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

It works fine. So, I have a Service layer as well, and My methods in this services accepts primitives types, like this:

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);
}

My doubts are:

Is it possible ? Ta add some conditions when necessary (when my parameters has value) ?

Thanks

/// my edits

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

   // others  conditions
}

/// Almost a solution

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 开发者_如何学Python(reserved.HasValue)
    filter = x => filter.Compile().Invoke(x) && x.Reserved == reserved.Value;

return _repository.FindAll(filter);


Here is a way to do this. I am not going to editorialize on what you are doing - it looks like query by example, which is almost always problematic. It is as the others here have best avoided. The expression thing is interesting though - so I thought it was worth a crack at it.

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

And we want to query it like this:

   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});

So all you have to do is write RemoveCloture(); - not a problem.

// 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;
}

}


First, I'd solve your problem by avoiding it in the first place. I'd have different methods for this.

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)

These all have trivially easy implementations in terms of a lambda expression. For example:

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);
}

etc.

Is it possible ? Ta add some conditions when necessary (when my parameters has value) ?

Second, yes what you want to do is possible but it's not the way I'd solve the problem.


Your repository method definition suggests that you see FindAll as something that you pass criteria in and get a completed result back. Why not instead just have the result be of type IQueryable and return Session.QueryOver?

Your service layer would then do something like this, chaining together the "wheres":


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();


So here is how you could actually and lambdas together - it borrows most of it's code from this awesome answer from desco that deserves an up-vote.

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);
    }
}

}

Then calling the code is quite simple ....

    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 });
0

精彩评论

暂无评论...
验证码 换一张
取 消