15
votes

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);
4
Are you asking how to build an expression tree?SLaks
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
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
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

4 Answers

12
votes

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);
1
votes

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.

0
votes

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!

0
votes

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"))