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.

sjb-sjb
  • 1,112
  • 6
  • 14
  • 1
    So you apparently have a working solution, and it's just two lines of code. So...what's the problem? – Servy Nov 13 '17 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 – sjb-sjb Nov 13 '17 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. – Servy Nov 13 '17 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. – sjb-sjb Nov 13 '17 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*. – Servy Nov 13 '17 at 19:27
  • Actually, my 'solution' doesn't seem to work. The type of the method seems to be incorrect. And besides the fact that it doesn't seem to work, it wasn't a real solution in the first place because it wasn't actually finding the requested method. Again, I don't think this deserves a downvote. Downvotes should be only for an "egregiously sloppy, no-effort-expended post", which clearly this was not. – sjb-sjb Nov 16 '17 at 02:54
  • If your solution doesn't work then you need to actually state what problems you're having with it, and how it isn't working, rather than saying you have a working solution if you actually don't. You of course still haven't done that; you don't actually have a question at all. – Servy Nov 16 '17 at 14:19

1 Answers1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    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 ! – sjb-sjb Nov 15 '17 at 02:34
  • 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 Nov 16 '17 at 02:58
  • @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. – dbc Nov 16 '17 at 04:50
  • 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). – sjb-sjb Nov 18 '17 at 04:08
  • P.S. @dbc I don't think we need an m x n matrix. – sjb-sjb Nov 18 '17 at 04:13