Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Generic Lambdas in LINQ

I'm driving myself crazy trying to understand Expressions in LINQ. Any help is much appreciated (even telling me that I'm totally off base here).

Let's say I have three classes

public class Person
{
    public string Name { get; set;}
    public IEnumerable<PersonLocation> Locations { get; set;}
    public IEnumerable<PersonEducation> Educations { get; set:}
}

public class PersonLocation
{
    public string Name { get; set;}
    public string Floor { get; set;}
    public string Extension { get; set;}
}

public class PersonEducation
{
   public string SchoolName { get; set;}
   public string GraduationYear { get; set;}
}

I'm trying to create a method that takes in a string, such as Locations.Name or Locations.Floor, or Educations.SchoolName which will then create a dynamic linq query

IEnumerable<Person> people = GetAllPeople();
GetFilteredResults(people, "Location.Name", "San Francisco");
GetFilteredResults(people, "Location.Floor", "17");
GetFilteredResults(people, "Educations.SchoolName", "Northwestern");

This GetFilteredResults(IEnumerable<Person> people, string ModelProperty, string Value) method should create an expression that is roughly equivalent to people.Where(p => p.Locations.Any(pl => pl.Name == Value);

I have this working if ModelProperty is a string, i.e. people.Where(p => p.Name == Value) looks like this:

string[] modelPropertyParts = ModelProperty.Split('.');
var prop = typeof(Person).GetProperty(modelPropertyParts[0]);
var sourceParam = Expression.Parameter(typeof(Person), "person");
var expression = Expression.Equal(Expression.PropertyOrField(sourceParam, modelPropertyParts[0]), Expression.Constant(option.Name));
var whereSelector = Expression.Lambda<Func<Person, bool>>(orExp, sourceParam);
return people.Where(whereSelector.Compile());

Here's what I have been playing around with for an IEnumerable type, but I just can't get the inner Any, which seems correct, hooked into the outer Where:

/*i.e. modelPropertyParts[0] = Locations & modelPropertyParts[1] = Name */
string[] modelPropertyParts = ModelProperty.Split('.');

var interiorProperty = prop.PropertyType.GetGenericArguments()[0];
var interiorParameter = Expression.Parameter(interiorProperty, "personlocation");
var interiorField = Expression.PropertyOrField(interiorParameter, modelPropertyParts[1]);
var interiorExpression = Expression.Equal(interiorField, Expression.Constant(Value));
var innerLambda = Expression.Lambda<Func<PersonLocation, bool>>(interiorExpression, interiorParameter);

var outerParameter = Expression.Parameter(typeof(Person), "person");
var outerField = Expression.PropertyOrField(outerParameter, modelPropertyParts[0]);
var outerExpression = ??
var outerLambda == ??

return people.Where(outerLambda.Compile());
like image 930
Daniel Ahrnsbrak Avatar asked Nov 04 '22 12:11

Daniel Ahrnsbrak


1 Answers

The problem is that System.Linq.Enumerable.Any is a static extension method.

Your outerExpression must reference System.Linq.Enumerable.Any(IEnumerable<T>, Func<T, bool>):

var outerExpression = Expression.Call(
    typeof(System.Linq.Enumerable), 
    "Any", 
    new Type[] { outerField.Type, innerLambda.Type }, 
    outerField, innerLambda);

Take a look at these links for more information:

  • MSDN Expression.Call(Type, String, Type[], params Expression[])
  • Some helpful, similar examples.
like image 196
3 revs Avatar answered Nov 09 '22 13:11

3 revs