15

I am trying to build a lambda expression that will be combined with others into a rather large expression tree for filtering. This works fine until I need to filter by a sub collection property.

How do you build a Lambda expression that will filter using Any() on a property of a collection which is a property of the root object?

Example:

CurrentDataSource.Offices.Where(o => o.base_Trades.Any(t => t.Name == "test"))

This is how I would build the expression statically but I need to build it dynamically. Sorry for the confusion.

Edit: Here is a snippet of how I handle the less complicated expressions:

IQueryable<Office> officeQuery = CurrentDataSource.Offices.AsQueryable<Office>();
ParameterExpression pe = Expression.Parameter(typeof(Office), "Office");
ParameterExpression tpe = Expression.Parameter(typeof(Trades), "Trades");

Expression SimpleWhere = null;
Expression ComplexWhere = null;
foreach (ServerSideFilterObject fo in ssfo)
{
    SimpleWhere = null;
    foreach (String value in fo.FilterValues)
    {
        if (!CollectionProperties.Contains(fo.PropertyName))
        {
            //Handle singleton lambda logic here.
            Expression left = Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName));
            Expression right = Expression.Constant(value);
            if (SimpleWhere == null)
            {
                SimpleWhere = Expression.Equal(left, right);
            }
            else
            {
                Expression e1 = Expression.Equal(left, right);
                SimpleWhere = Expression.Or(SimpleWhere, e1);
            }
        }
        else
        {
            //handle inner Collection lambda logic here.
            Expression left = Expression.Property(tpe, typeof(Trades).GetProperty("Name"));
            Expression right = Expression.Constant(value);
            Expression InnerLambda = Expression.Equal(left, right);

            //Problem area.
            Expression OfficeAndProperty = Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName));
            Expression OuterLambda = Expression.Call(OfficeAndProperty, typeof(Trades).GetMethod("Any", new Type[] { typeof(Expression) } ),InnerLambda);

            if (SimpleWhere == null)
                SimpleWhere = OuterLambda;
            else
                SimpleWhere = Expression.Or(SimpleWhere, OuterLambda);
        }
    }
    if (ComplexWhere == null)
        ComplexWhere = SimpleWhere;
    else
        ComplexWhere = Expression.And(ComplexWhere, SimpleWhere);
}
MethodCallExpression whereCallExpression = Expression.Call(typeof(Queryable), "Where", new Type[] { officeQuery.ElementType }, officeQuery.Expression, Expression.Lambda<Func<Office, bool>>(ComplexWhere, new ParameterExpression[] { pe }));
results = officeQuery.Provider.CreateQuery<Office>(whereCallExpression);
joce
  • 9,624
  • 19
  • 56
  • 74
George
  • 430
  • 1
  • 4
  • 16
  • Are you asking how to build an expression tree? – SLaks Jan 23 '12 at 20:12
  • I'm not sure how the hierarchy works in your example. Can you elaborate a little more on that? Is Offices the root and then each Office has a collection of Trades? And you want to filter on the name of the trade?? The filter is where I'm a little lost. Sorry. – David Hoerster Jan 23 '12 at 20:15
  • No, I am just unsure of the syntax used to build an expression with an internal method call and an expression for a parameter. In this case, I am getting an error stating that Any() can't be found because my parameters don't match the definition. In this case I am not sure if that is because I am off on the syntax or if Any() is not supported in the way I am using it. – George Jan 23 '12 at 20:18
  • As far as the hierarchy: Offices is the root and each office has a collection of trades. I am attempting to filter the collection based on the Name property of each trades object in the collection. – George Jan 23 '12 at 20:20

4 Answers4

12

Found the solution. I wasn't looking for the any method in the right place before.

Expression left = Expression.Property(tpe, typeof(Trades).GetProperty("Name"));
Expression right = Expression.Constant(value);
Expression InnerLambda = Expression.Equal(left, right);
Expression<Func<Trades, bool>> innerFunction = Expression.Lambda<Func<Trades, bool>>(InnerLambda, tpe);

method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(Trades));
OuterLambda = Expression.Call(method, Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName)),innerFunction);
George
  • 430
  • 1
  • 4
  • 16
1

Please don't do this, what you really want it to use a library called dynamic linq. http://nuget.org/packages/DynamicLINQ

You can just store your queries as strings, and it supports very complex querying. Expression trees are a nightmare.

Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75
  • that is an excellent library, I use it too, but it does not support for example sorting on a property of a collection property like `myIQueryable.OrderBy(x => x.MyCollection.Select(y => y.Myproperty))`, at least I cannot get it to work – Daniël Tulp Oct 11 '17 at 09:45
0

What you listed as your example will work based on your comment. Here's an example of what I work with:

Templates.Where(t => t.TemplateFields.Any(f => f.Required == 'Y'))

We have templates that have specific collection of fields, and those fields could be required. So I can get the templates where any field is required by that statement above.

Hopefully this helps...or at least confirms what you're trying to do. Let me know if you have more questions about this and I'll elaborate.

Good luck!

David Hoerster
  • 28,421
  • 8
  • 67
  • 102
  • This is similar to what I am working with but I have to build the lamda expression dynamically with reflection so that I can ensure the filter incorporates the other filters in the set. – George Jan 23 '12 at 21:06
0

The provided code

CurrentDataSource.Offices.Where(o => o.base_Trades.Any(t => t.Name == "test"))

should work, as long as o.base_Trades implements IEnumerable<Trade>. If o.base_Trades does only implement IEnumerable, you need to use either Cast<Trade>() if you can be sure that all elements in o.base_Trades are of your Trade type or OfType<Trade>() if there might be elements of other (incompatible) types.

That would then look like this:

CurrentDataSource.Offices
    .Where(o => o.base_Trades.Cast<Trade>.Any(t => t.Name == "test"))
Nuffin
  • 3,882
  • 18
  • 34