Reputation: 430
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);
Upvotes: 15
Views: 10830
Reputation: 10839
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.
Upvotes: 1
Reputation: 430
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);
Upvotes: 12
Reputation: 3972
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"))
Upvotes: 0
Reputation: 28701
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!
Upvotes: 0