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.
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.
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.
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
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'
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");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With