How does .Any() access entity property when built with an expression tree?

65 Views Asked by At

First of all, this code works but I don't understand why it works. This question is asking why I can access a property in an expression tree when I don't think I should.

Let's start by establishing context.

Context

I set up two example classes that are included in my dbcontext. They do not have virtual navigation properties. This is a limitation of my legacy dbcontext. The entities in my context are different but I'm using these as an example.

class Person 
{
    int PersonId {get; set;}
    string FirstName {get; set;}
}

class Address
{
    int AddressId {get; set;}
    string Address {get; set;}
    string City {get; set;}
    string State {get; set;}
    string Zip {get; set;}
    int PersonId {get; set;}
}

Let's say that I have a function that returns type Expression<Func<Person, bool>> This function helps build expressions that are used by entity framework. I need to build a filter that "joins" address to person using an Any. The predicate version of this would look something like this.

Expression<Func<Person, bool>> myFilter = x => myDbContext.Address.Any(y => y.PersonId == x.PersonId && y.City == "New York");

Since I need to join to various tables that aren't necessarily the Address table, I was able to build this with a generic Expression Tree like this:

Expression<Func<Person, bool>> BuildMyExpression<T>(IQueryable<T> passedInGenericQueryable)
{
    // This variable is passed in to the function. It's a generic but I'm showing the concrete type here for this example.
    //IQueryable<Address> passedInGenericQueryable;
    
    // This build an expression tree for y.City == "New York" but I omitted the details because my question isn't about this
    Expression<Func<T, bool>> compareCity = MagicFunction<T>("New York", "City");
    
    var personParam = Expression.Parameter(typeof(Person), "Person");
    var addressParam = Expression.Parameter(typeof(T), "Address"); // I named this address for readability but it could be several different types
    
    // Compares the PersonId on the Person table and any other table that has a PersonId property
    var personIdEqual = Expression.Equal(
        Expression.Property(personParam, "PersonId"),
        Expression.Property(addressParam, "PersonId")
    );
    
    // Ands the city comparison and the personId comparison
    var andedExpressions = Expression.AndAlso(
        personIdEqual,
        Expression.Invoke(compareCity, addressParam)
    );
    
    var lambdaExpression = Expression.Lambda<Func<T, bool>>(andedExpressions, addressParam);
    
    
    // Create an expression tree to represent the Any method on IQueryable<Address>
    var anyMethod = typeof(Queryable).GetMethods()
        .Where(m => m.Name == "Any" && m.GetParameters().Length == 2)
        .Single()
        .MakeGenericMethod(typeof(T));
    
    var callAnyExpression = Expression.Call(
        anyMethod,
        Expression.Constant(passedInGenericQueryable),
        lambdaExpression
    );
    
    Expression<Func<Person, bool>> fullExpression = Expression.Lambda<Func<Person, bool>>(callAnyExpression, personParam);
    
    
    return fullExpression;
}

Question

My question is specifically about the lines that builds the Any method.

// Create an expression tree to represent the Any method on IQueryable<Address>
var anyMethod = typeof(Queryable).GetMethods()
    .Where(m => m.Name == "Any" && m.GetParameters().Length == 2)
    .Single()
    .MakeGenericMethod(typeof(T));
        
var callAnyExpression = Expression.Call(
        anyMethod,
        Expression.Constant(passedInGenericQueryable),
        lambdaExpression
    );

How are these lines able to access the properties on the Person entity?

If I look at the definition of the Any method it looks like this:

public static bool Any<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

It's an extension method that takes in an Expression<Func<TSource, bool>> and returns a bool When I'm building my expression, I pass in IQueryable<Address> but it accesses a member on Person E.G.

var personIdEqual = Expression.Equal(
        Expression.Property(personParam, "PersonId"),
        Expression.Property(addressParam, "PersonId")
    );

If the collection it's operating on is IQueryable<TSource> (Address in this case), how is it able to access the PersonId property on Person?

My question is very similar to this question but not quite the same.

0

There are 0 best solutions below