0

In EntityFrameworkQueryableExtensions there are two methods, both called ThenInclude, with the following signatures:

public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousProperty, TProperty>(this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath)where TEntity : class

and

public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousProperty, TProperty>(this IIncludableQueryable<TEntity, IEnumerable<TPreviousProperty>> source, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath)where TEntity : class

The difference is that the second signature has IEnumerable<TPreviousProperty> in the type of the 'this' argument, while the first signature has just TPreviousProperty.

The question is, how can I get one the MethodInfo for the second one (or the first one for that matter) using reflection and MakeGenericMethod?

So far all I've been able to come up with is to add an extra layer like this:

class Whatever<TEntity> where TEntity: class {
    private static MethodInfo ThenIncludeEnumerableMethod<TPreviousProperty,TProperty>()
    {
        Func<IIncludableQueryable<TEntity,IEnumerable<TPreviousProperty>>, Expression<Func<TPreviousProperty, TProperty>>, IIncludableQueryable<TEntity, TProperty>> thenIncludeLambda = (source, lambda) => source.ThenInclude(lambda);
        return thenIncludeLambda.Method;
    }
}

There should be a more direct way to do it.

Note Reflection: How to get a generic method? is not an answer to this question.

7
  • 1
    So you apparently have a working solution, and it's just two lines of code. So...what's the problem? Commented Nov 13, 2017 at 17:02
  • The main problem is that if there is no direct way to do this by reflection then there is a gap in the Reflection API. Another is that this solution is less efficient than a direct Reflection solution inasmuch as it requires Commented Nov 13, 2017 at 19:11
  • So you've profiled this code and determined that it's actually a performance problem for you application? How long is it taking to execute, and what are the requirements of your application? Again, the whole point is you already have a working solution. You have no need for a different solution. If/when you have a demonstrated problem with this solution, then start looking for an alternate solution that won't have whatever problem(s) this one is giving you. Commented Nov 13, 2017 at 19:13
  • One problem is that if there is no direct way to do this by reflection then there is a gap in the Reflection API. Another concern is that this solution cannot be used with types that are only known at runtime. And third, arguably this solution also is less efficient in some respects. I would ask you to reverse your down-vote. Even if you don't happen to like the question, it is a valid question and not one that has an obvious answer. If you do have an answer then of course I would be interested to see it. Commented Nov 13, 2017 at 19:17
  • You are able to solve the problem though. You don't have a problem that you can't solve, because you've already solved it. This solution is almost certainly going to be faster than any alternative. The odds of this being insufficiently performant are basically zero, but if it's not good enough for you, then there almost certainly is no solution, as anything else is pretty much going to be worse (outside of perhaps caching results of your existing solution). Again, if you actually have a problem, then people can help you solve it. Currently you don't have a problem. Commented Nov 13, 2017 at 19:27

1 Answer 1

0

You would like to call

typeof(EntityFrameworkQueryableExtensions).GetMethod("ThenInclude", new [] { type1, type2 })

But you cannot do so because the type arguments type1 and type2 are constructed from the open generic types returned by MethodInfo.GetGenericArguments() for the two methods named "ThenInclude".

What you can do is to loop through all methods named "ThenInclude" that have the required number of generic arguments and parameters, map the open generic arguments to the required parameter type(s), and check if the actual parameters types match the required types:

class Whatever<TEntity> where TEntity : class
{
    public static MethodInfo ThenIncludeMethod<TPreviousProperty, TProperty>()
    {
        var query = from m in typeof(EntityFrameworkQueryableExtensions).GetMethods()
                    where m.Name == "ThenInclude" && m.IsGenericMethodDefinition
                    let args = m.GetGenericArguments()
                    where args.Length == 3
                    let tEntityType = args[0]
                    let tPreviousPropertyType = args[1]
                    let tPropertyType = args[2]
                    let parameters = m.GetParameters()
                    where parameters.Length == 2
                    where parameters[0].ParameterType == typeof(IIncludableQueryable<,>).MakeGenericType(new Type[] { tEntityType, tPreviousPropertyType })
                    where parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(new[] { tPreviousPropertyType, tPropertyType }))
                    select m.MakeGenericMethod(new[] { typeof(TEntity), typeof(TPreviousProperty), typeof(TProperty) });
        return query.SingleOrDefault();
    }

    public static MethodInfo ThenIncludeEnumerableMethod<TPreviousProperty, TProperty>()
    {
        var query = from m in typeof(EntityFrameworkQueryableExtensions).GetMethods()
                    where m.Name == "ThenInclude" && m.IsGenericMethodDefinition
                    let args = m.GetGenericArguments()
                    where args.Length == 3
                    let tEntityType = args[0]
                    let tPreviousPropertyType = args[1]
                    let tPropertyType = args[2]
                    let parameters = m.GetParameters()
                    where parameters.Length == 2
                    where parameters[0].ParameterType == typeof(IIncludableQueryable<,>).MakeGenericType(new Type[] { tEntityType, typeof(IEnumerable<>).MakeGenericType(new[] { tPreviousPropertyType }) })
                    where parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(new[] { tPreviousPropertyType, tPropertyType }))
                    select m.MakeGenericMethod(new[] { typeof(TEntity), typeof(TPreviousProperty), typeof(TProperty) });
        return query.SingleOrDefault();
    }
}

Honestly your current method is much simpler, however it does return the MethodInfo of an anonymous method declared by Whatever<T> that calls EntityFrameworkQueryableExtensions.ThenInclude() while the above return a MethodInfo of a concrete method of EntityFrameworkQueryableExtensionsThenInclude<,,>() directly.

Note I wrote this using full .Net. If you are using .Net Core you may need to nuget System.Reflection.TypeExtensions as described here and then I believe it should work.

Sign up to request clarification or add additional context in comments.

5 Comments

I guess that with a bit of work one could make this more general, i.e. GetGenericMethod( string methodName, Type[] typeParameters, Type parameterTypes) by walking down each of the parameter types to look for the type parameters, then seeing if this matches the parameters in the current method. A huge pain in the neck though to do that !
Per the comment I added above, my proposed approach doesn't seem to work. Would be great to have a more generic :-) solution. I can't really even see how to specify the signature of a general solution, though. It would be logical to pass functions that map the type parameters to the argument types. But if you do this then the caller has to write their own expression for these functions, using for example MakeGenericType. That would seem to defeat the purpose.
@sjb-sjb - i think you would need to pass in an n x m matrix of functions mapping the m open generic parameters to the n method arguments. But like you I couldn't think of a pretty way to do it, so I just gave a simple answer instead.
Perhaps we need a more general extension method, static Type CloseWith( this Type t, params Type[] typeParams) that inserts the typeParams into the first typeParams.Count open slots in the open type t (and no error if there are too many typeParams). Then we could have the signature MethodInfo GetGenericMethod( string methodName, Type[] typeParams, Type[] parameterTypes) where parameterTypes are closed types. Then in the query we could check parameters.Length = parameterTypes.Length and for all j, parameterTypes[j] == parameters[j].ParameterType.CloseWith( typeParams).
P.S. @dbc I don't think we need an m x n matrix.

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.