5

Background

I need to be able to build a .Include that filters results down to records created on or before a specific date. I will not know the types of the objects on the original LINQ statement, and I will be finding them (through a whole lot of hoops, but that is working).

I have a graph of objects like this:

          A -> Ac
         /  \
  Bc <- B    \
       /      \  
Cc <- C        D -> Dc

Objects Ac, Bc, Cc, and Dc are all found dynamically (that is, a developer does not type in these relationships when writing the original linq statement). These then need to be added to an IQueryable expression and only return values up to a specific DateTime.

The Code So Far

I tried building a function that constructs a lambda and compares dates. So far, if the original query is A.Include(x => x.B).Include(x => x.B.Select(y => y.C), I can construct the string "A.B.Bc", so I know the types of A, B, and Bc, and the way they relate.

private static IQueryable<T> FilterChangeTrackerToDate<T>(this IQueryable<T> query, string includeString, DateTime targetDateTime)
{
    var param = Expression.Parameter( typeof( T ), "x" );

    var prop = Expression.Property(param, includeString + ".CreatedUtc");

    var dateConst = Expression.Constant(targetDateTime);

    var compare = Expression.LessThanOrEqual(prop, dateConst);

    var lambda = Expression.Lambda<Func<T, bool>>(compare, param);

    return query.Where(lambda);
}

The issue is that the above code crashes because "A.B.Bc.CreatedUtc" isn't the correct way to load the date property - I need to traverse this through .Select statements.

The Problem

In order to both load the records and filter them, I need to dynamically build a .Select to pull out the values I need (which, thankfully, are static based on inheritance) and put those values into an anonymous type, which can then be filtered with a .Where(). All of this needs to be done with minimal generics, as I don't have compile-time types to validate against and I'd have to abuse reflection to get them to work.

Essentially, I need to create this:

.Select( x => new
{
    Bc = x.B.Select(z => z.Bc.Select(y => y.CreatedUtc).Where( q => q > DateTime.UtcNow ) ),
    A= x
} )

dynamically, using the string "A.B.Bc", for any level of nesting.

Resources

This is where I saw how to filter an EF include method: LINQ To Entities Include + Where Method

This post talks about dynamically creating a Select, but it is only selecting top-level values and does not seem like it can build the rest of the parts needed for my problem: How to create LINQ Expression Tree to select an anonymous type

Dynamic Linq

Using the System.Linq.Dynamic library, I have tried to access the CreatedUtc value off of Bc:

query = (IQueryable<T>) query.Select(includeString + ".CreatedUtc");

Where includeString = "B.Bc", but this gives me an exception of:

Exception thrown: 'System.Linq.Dynamic.ParseException' in System.Linq.Dynamic.dll

Additional information: No property or field 'Bc' exists in type 'List`1'

1 Answer 1

2

I believe System.Linq.Dynamic is what you need. It's a library originally written by ScottGu from Microsoft, and it allows you to build LINQ query from string like that:

IQueryable nestedQuery = query.Select("B.Bc.CreatedUtc");

where query is IQueryable<A>.

There is a number of forks of this library. You can start from here. Documentation on query language is here. Also here there is a version with some additional functionality and here is dotnet.core version.

UPD1: This library lacks of SelectMany extension method but this one has. So with latter library you can do the following:

query.SelectMany("B").SelectMany("Bc").Select("CreatedUtc");
Sign up to request clarification or add additional context in comments.

7 Comments

When I include the string "B.Bc.CreatedUtc", I get this error: Additional information: No property or field 'Bc' exists in type 'List1'`. It doesn't look like it's exploring nested graphs correctly?
Yeah, all of my foreign key relationships are collections for this purpose - though they may be 1-1 in the future, so ideally the solution would be able to accommodate both.
@Max have you included them? (.Include(...))
@Max you have to tell EF to load your dependencies, or they will not be accessable.
If you mean on the incoming original IQueryable, then B is Included but Bc is not.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.