2

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 (reserved.HasValue)
    filter = x => filter.Compile().Invoke(x) && x.Reserved == reserved.Value;

return _repository.FindAll(filter);
6
  • I know a way you can do this - but it would take me a a little while to implement. but you could just do a x=>(!string.IsNullOrEmpty(name)||name==x.Name)&&(stock.HasValue||x.Stock==stock??0) ... etc What I could add for you is a way to replace the cloture with constants and then shortcut the logical evaluation so that the expression ends up as short as you want.
    – Neil
    Commented Jan 20, 2011 at 19:49
  • Hi Neil, If it was possibile, I would like to see some code if you can help me =D. I imagined this way you said, and I will study this possibility. Thank you! Commented Jan 21, 2011 at 10:33
  • @Felipe See my answer - there you go.
    – Neil
    Commented Jan 21, 2011 at 15:42
  • Ok I think you are going to have a problem with your proposed solution. The problem is that once you call Compile() and Invoke() you will loose the Meta Data that nhibernate is relying on to build a query from. Essentially you will get something that can only be evaluated on the client and not something that will be convertible to SQL.
    – Neil
    Commented Jan 21, 2011 at 18:12
  • Thanks Neil, I'll choose your solution. Thanks =D Commented Jan 21, 2011 at 18:17

4 Answers 4

2

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

}

2
  • Hi Neil, very nice solutuion, I'll check with my team the viability to do it. But I would like to know your opnion about the solution that I write in my first post, take a look and give me your opinion if is it a good solution ?! Thanks man! Commented Jan 21, 2011 at 17:53
  • My comment is attached to your question.
    – Neil
    Commented Jan 21, 2011 at 18:13
1

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.

1
  • Hi Jason, thanks, but in my presentation when I need to invoke the Service, Will I need to check each parameters ? (Look my edits in first post). I'd like to have one method and pass the parameters and it... I have some moments that I'll have 10 parameters for a filter, and It can be combinated... for this I ask if is possible to do a dynamic expression... Thanks! Commented Jan 20, 2011 at 17:16
0

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

1
  • 1
    It's a good solution too, but I preffer to keep this responsability to my repository. Commented Jan 21, 2011 at 16:07
0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.