mercredi 4 novembre 2020

Dynamically access property nested in anonymous type within LINQ query

I am executing a lengthy LINQ query on my EF-mapped DB entity classes that is treated by Linq2Sql and ends up with an anonymous type:

...
.Where(e => ...)
.Join(..., (outer, inner) => new {
    outer.Object1,
    outer.TempId,
    Object2 = inner
})

So far, so good - up to that point, everything will nicely be translated to SQL and run on the DB. I can iterate over the anonymously typed objects, at which point only the very data contained therein will be materialized on the C# side.

However: I now need to perform one last filtering step that will reduce the data set, which is why it should run on the DB, as well. I need to compare a property on Object1 and Object2.

The name of this property is passed in to my method as a string.

Under normal circumstances, this wouldn't be a problem. I could simply access the anonymous object via reflection - even if it means I have to get the anonymous type by means of .GetType().GetGenericArguments()[0] on my enumerable.

However, I need to make sure everything's typesafe here, so the ensuing foreach will let me iterate over and work with instances of the anonymous type. Therefore, creating a filter expression is not straightforward as I cannot specify a type for Lambda<TDelegate> - it would have to be a Func<..., Boolean>, where ... is my anonymous type!

I have thought about creating a project function instead that takes an instance of the type that Object1 and Object2 in my anonymous type belong to - say, ObjectData, - and returns the property value. This would, of course, be feasible:

var prm = Expression.Parameter(typeof(ObjectData), "obj");
var projectionExpr = Expression.Lambda<Func<ObjectData, int>>(Expression.Property(prm, myPropertyName), prm);

But then I'm not sure where to apply them - I'd need some kind of a "single-item select", that just projects one value to another by means of a projection expression and that will play well with EF's LINQ to Entities/SQL generation - something like (pseudocode, with an imaginary PROJECT function) this:

.Select(info => new {
    info.Object1,
    info.TempId,
    info.Object2,
    Value1 = PROJECT(info.Object1, projectionExpr),
    Value2 = PROJECT(info.Object2, projectionExpr)
})

How do I get my final filter into the story?





Aucun commentaire:

Enregistrer un commentaire