How to match method with a complex generic parameter using reflection? - c#

I am trying to call a EntityFramework method using reflection.
The signature of the method is
DbCollectionEntry<TEntity, TElement> Collection<TElement>
(Expression <Func<TEntity, ICollection <TElement>>> navigationProperty) where TElement : class
where TEntity is a type parameter of the class. How can construct a type which which will equal Expression <Func<TEntity, ICollection <TElement>>> so I can find the right overload of the method? I tried typeof(Expression<>).MakeGenericType(new[] { typeof(Func<,>) }); which displays the same in the debugger but doesn't match by equality. Once I have the method how do I call it? I managed to find the method by excluding the one with a string parameter but I still want find out how to match the method properly. I tried invoking the method using method.MakeGenericMethod(typeof(MyType)).Invoke(context.Entry(t), new[] { lambda }); but I get the exception "Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true." Here's what I have so far
var collectionMethod = typeof(DbEntityEntry<>)
.GetMethods()
.Where(m => m.Name == "Collection")
.Select(m => new { Method = m, Params = m.GetParameters(), Args = m.GetGenericArguments() })
.Where(x => x.Args.Length == 1 && x.Params[0].ParameterType != typeof(string))
.Select(x => x.Method)
.First();
var lambda = Expression.Lambda<Func<Trade, ICollection<TradeLeg>>>(prop, param);
// this fails
var o = collectionMethod.MakeGenericMethod(typeof(TradeLeg))
.Invoke(context.Entry(t), new[] { lambda });

var collectionMethod = typeof(DbEntityEntry<>)
This line condemns you to a failure, because the method will be looked for on the generic definition, not on the constructed type you use. The method must be identified on the concrete type, not on the definition, in order to call it. Based on your code, I assume the correct expression is typeof(DbEntityEntry<Trade>).

Related

Create an expression dynamically to sort Linq to Entities ascending/descending based on a dynamically provided property name

Here is what I am trying to do. I have done a few simple expressions, but this one is a little too much for me right now.
public static Expression<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>> SortMeDynamically<TEntityType>(bool isAsc, string propertyname)
{
var param = Expression.Parameter(typeof(TEntityType), "x");
var prop = Expression.PropertyOrField(param, propertyname);
var sortLambda = Expression.Lambda(prop, param);
string sortOrder = isAsc ? "OrderBy" : "OrderByDescending";
var selector = Call(
typeof(Queryable),
sortOrder,
new[] { prop.Type},
sortLambda);
var lambda = Lambda<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>>(selector, param);
return lambda;
}
The error I get is the following.
System.InvalidOperationException: No generic method 'OrderBy' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic
The closest solution I found was the one below.
LINQ to Entities OrderBy Expression Tree
Calling the various .OrderBy linq methods dynamically is quite a pain.
The most difficult part of this process is locating the MethodInfo of the four relevant Queryable.OrderBy methods, with the correct generic constraints. There are a number of ways to achieve this.
You could pull the method out of a template Expression<Func<...>>, as in your linked answer;
Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod =
(() => query.OrderBy<TEntityType, object>(k => null));
var methodCallExpression = (sortMethod.Body as MethodCallExpression);
var method = methodCallExpression.Method.GetGenericMethodDefinition();
You could use Type.GetMethods and filter the results. Though you'd have to worry about future runtime changes breaking this approach.
var method = typeof(Queryable).GetMethods()
.Where(m => m.IsGenericMethod
&& m.Name == nameof(Queryable.OrderBy)
&& m.GetParameters().Length == 2)
.Single();
In both cases you'd then need to call .MakeGenericMethod to supply the correct generic parameters.
var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
Or you could create a delegate and pull the method from there. Again, getting the generic constraints correct is a bit fiddly. But can be easier with a helper method. Which is similar to how the linq runtime locates this MethodInfo.
public MethodInfo GetOrderFunc<T, V>(Func<IQueryable<T>, Expression<Func<T, V>>, IOrderedQueryable<T>> func)
=> func.Method;
var genericSortMethod = GetOrderFunc<TEntityType, V>(Queryable.OrderBy);
If you don't know the argument value type, you could call that method via reflection.
Now you can either invoke the method;
var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(null, new object[] { query, sortLambda });
Or by reading the source code, recreate what those methods actually do.
var expression = query.Expression;
expression = Expression.Call(
typeof(Queryable),
genericSortMethod,
new Type[] { typeof(TEntityType), prop.Type },
expression,
Expression.Quote(sortLambda));
var orderedQuery = (IOrderedQueryable<T>)query.Provider.CreateQuery<T>(expression);
No matter how you approach this, you need to take an IQueryable<TEntity> query parameter, and return an IOrderedQueryable<TEntity>. Or just stop at creating the Expression<Func<>>.
The other, other option is to move all the generic mucking around into a helper method. Then invoke that method via reflection. Obtaining the generic MethodInfo using one of the same approaches explained above.
public IOrderedQueryable<T> Order<T, V>(this IQueryable<T> query, Expression<Func<T, V>> key, bool then, bool desc)
=> (desc)
? (then ? ((IOrderedQueryable<T>)query).ThenByDescending(key) : query.OrderByDescending(key))
: (then ? ((IOrderedQueryable<T>)query).ThenBy(key) : query.OrderBy(key));

Call Any inside Expression and pass the delegate

I have a scenario here where I need to hit a dynamic query using linq (with nhibernate). The final query should look like this:
long[] values = { ... };
var result = Queryable<Entity>.Where(x => x.Documents.Any(d => values.Contains(d.Id)))
.ToList();
The generic Entity and the property Documents can change and it will be defined by some user configurations. The type of collection Documents is ICollection<T> where T is Document type. I am trying to create an Expression tree to define these statements dynamically but I am getting some issues. Look the code and comments bellow of what I have tried.
I have create this function to return the delagate I want to use inside the Any method:
public static Func<T, bool> GetFunc<T>(long[] values)
where T : Entity
{
return x => values.Contains(x.Id);
}
And I am using the Expression class to make the expression like this (see code and comments):
// define my parameter of expression
var parameter = Expression.Parameter(typeof(T), "x");
// I get an array of IDs (long) as argument and transform it on an Expression
var valuesExpression = Expression.Constant(values);
// define the access to my collection property. propertyFilter is propertyinfo for the `Documents` of the sample above.
// I get an expression to represent: x.Documents
var collectionPropertyExpression = Expression.Property(parameter, propertyFilter);
// get the T generic type of the ICollection<T> from propertyFilter. I get the `Documents` of sample above.
var entityFilterType = propertyFilter.PropertyType.GetGenericArguments()[0];
// get the definition of `Any` extension method from `Enumerable` class to make the expression
var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(x => x.Name == "Any" && x.GetParameters().Length == 2)
.MakeGenericMethod(entityFilterType);
// get a methodBase for GetFunc to get the delagete to use inside the Any
// using the `Document` generic type
var collectionBody = typeof(LookUpHelper).GetMethod("GetFunc", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(entityFilterType);
// call the any passing the collection I need and convert it to a Delegate
// I get something like: x => values.Contains(x.Id) ... where x if the `Document`
var func = (Delegate)collectionBody.Invoke(null, new object[] { values });
// get the func as an expression .. maybe the problem is here
var funcExpression = Expression.Constant(func);
// call the any passing the collection and my delagate as arguments
var f = Expression.Call(anyMethod, collectionPropertyExpression, funcExpression);
// I already have an expression and concatenate it using `AndAlso` operator.
body = Expression.AndAlso(body, f);
// finally, I built up to lambda expression and apply it on my queryable
var filterExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
var result = Queryable.Where(filterExpression).ToList();
It executes until the query be executed by ToList method. I am getting the following error:
Could not parse expression
'x.Documents.Any(value(System.Func`2[Project.Document,System.Boolean]))':
The object of type 'System.Linq.Expressions.ConstantExpression' cannot
be converted to type 'System.Linq.Expressions.LambdaExpression'. If
you tried to pass a delegate instead of a LambdaExpression, this is
not supported because delegates are not parsable expressions.
I am not sure what I am doing wrong. Someone can help me?
Thank you.
You are passing a Func where an Expression<Func> is expected. The former is a delegate and the latter is an expression.
public static Expression<Func<T, bool>> GetFunc<T>(long[] values)
where T : Entity
{
return x => values.Contains(x.Id);
}
Now you forego needing to build the expression manually with your expression helper class since you already have the expression.

Stubbing function expression of repository object using Rhino Mocks

I'm using Rhino Mocks to unit test a service method that makes the following call:
var form = Repo.GetOne<Form>(f => f.Id == formId, "FormTemplate, Event");
The Repo.GetOne method signature is:
TEntity GetOne<TEntity>(
Expression<Func<TEntity, bool>> filter = null,
string includeProperties = null)
where TEntity : class, IEntity;
So far I have only managed to do this by ignoring the Function Expression argument:
_mockRepo.Stub(
r =>
r.GetOne<Form>(
Arg<Expression<Func<Form, bool>>>.Is.Anything,
Arg<string>.Is.Equal("FormTemplate, Event")))
.Return(form);
How do I stub the Repo.GetOne method in order to set the return value when the method is called with arguments f => f.Id == formId, "FormTemplate, Event"?
When you setup Stub/Mocks, the value used for the arguments must return true when you call mockArg.Equals(runtimeArg). Your Func<> isn't going to do that, so you're best bet is to use the Arg<T>.Matches() constraint, which takes a function that, given the stub inputs, returns true if the runtime input are a match.
Unfortunately, looking at the contents of the Func<T> delegate is not straightforward. (Take a look at https://stackoverflow.com/a/17673890/682840)
var myArgChecker = new Function<Expression<Func<Form, bool>>, bool>(input =>
{
//inspect input to determine if it's a match
return true; //or false if you don't want to apply this stub
});
_mockRepo.Stub(
r =>
r.GetOne<Form>(
Arg<Expression<Func<Form, bool>>>.Matches(input => myArgChecker(input)),
Arg<string>.Is.Equal("FormTemplate, Event")))
.Return(form);

How can I get a protected method via reflection matching name and parameters type?

Before dotnet core we were able to do
var member = type.GetMethod(name, bindingFlags, null, argtypes, null);
To get access to a method matching its name and parameters type, in dotnet core they removed this overload, now I can only get by name && binding flags OR name && parameters type (see), but not like before.
There is a new method GetRuntimeMethods which returns IEnumerable<MethodInfo> and includes non public methods but I can't filter by parameters type.
There is another method GetRuntimeMethod which I can filter by parameters type but it doesn't include non public methods.
I already tried something like this, but fails
var member = type.GetRuntimeMethods().Where(m =>
m.Name == name && (m.GetParameters().Select(p => p.GetType()).ToArray() == argtypes)).FirstOrDefault();
Is there a way to get a method by its name and parameters type?
Yes, that overload is indeed missing. What you can use is to use GetMethods() and filter the output the way you want. Your attempt is close, except you can't compare arrays using ==:
var method = type.GetMethods().FirstOrDefault(m =>
m.Name == name && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(argTypes));

How to use typeof(object).GetMethod() with lambda parameter

How does one use Type.GetMethod() to get a method with lambda parameters? I'm trying to get the Queryable.Any method for a parameter of something like Func, using this:
typeof(Queryable).GetMethod("Any", new Type[]{typeof(Func<ObjType, bool>)})
but it keeps returning null.
There are four things wrong:
There's no such thing as a "lambda parameter". Lambda expressions are often used to provide arguments to methods, but they're converted into delegates or expression trees
You've missed off the first parameter of Queryable.Any - the IQueryable<T>
You're using Func<ObjType, bool> which is a delegate type, instead of Expression<Func<ObjType, bool>> which is an expression tree type
You can't get at generic methods in quite that way, unfortunately.
You want:
var generic = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.Where(m => m.GetParameters().Length == 2)
.Single();
var constructed = generic.MakeGenericMethod(typeof(ObjType));
That should get you the relevant Any method. It's not clear what you're then going to do with it.

Categories