Building a dynamic expression tree to filter on a collection property
Asked Answered
S

4

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);
Sapindaceous answered 23/1, 2012 at 20:8 Comment(4)
Are you asking how to build an expression tree?Kerf
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.Secretive
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.Sapindaceous
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.Sapindaceous
S
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);
Sapindaceous answered 24/1, 2012 at 16:18 Comment(0)
H
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.

Hola answered 24/1, 2012 at 18:57 Comment(1)
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 workAnacrusis
S
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!

Secretive answered 23/1, 2012 at 20:30 Comment(1)
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.Sapindaceous
B
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"))
Brubaker answered 23/1, 2012 at 20:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.