mardi 20 mars 2018

C# Execute linq method by reflection

I would like to create a method for generate an orderby expression with a count linq method.

i use the following data objects

public class SubData
{
    public Name { get; set; }
}

public class Data
{
    public ICollection<SubData> subDatas { get; set; }
}

I use the following method to generate an orderby expression with simple property name parameter. (x => x.PropertyName) and that make the job.

private static IOrderedQueryable<TSource> OrderBy<TSource>(IQueryable<TSource> query, string propertyName, string orderMethod)
{
    Type entityType = typeof(TSource);

    // Create x => x.PropName
    PropertyInfo propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression property = Expression.Property(arg, propertyName);
    LambdaExpression selector = Expression.Lambda(property, arg);

    // Get System.Linq.Queryable.OrderBy() method.
    Type enumarableType = typeof(Queryable);

    MethodInfo method = enumarableType
        .GetMethods()
        .Where(m => m.Name == orderMethod && m.IsGenericMethodDefinition)
        .Where(m =>
        {
            List<ParameterInfo> parameters = m.GetParameters().ToList();
            // Put more restriction here to ensure selecting the right overload that has 2 parameters
            return parameters.Count == 2;
        }).Single();

    if (propertyInfo == null)
    {
        throw new ArgumentException(nameof(propertyName));
    }

    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
    MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

    /* Call query.OrderBy(selector), with query and selector: x=> x.PropName
       Note that we pass the selector as Expression to the method and we don't compile it.
       By doing so EF can extract "order by" columns and generate SQL for it. */
    return (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, selector });
}

Now i would like to create an other method to create (x => x.PropertyName.Count()) expression. I try to get the count method from Queryabe class and i think it's the correct way. But when i try to Call the count method for my instance i have this error :

A static method requires a null instance, while a non-static method requires a non-null instance.

I don't understand why i can't call the static count method on my subDatas collection.

private static IOrderedQueryable<TSource> OrderByCount<TSource>(IQueryable<TSource> query, string propertyName, string orderMethod)
{
    Type entityType = typeof(TSource);

    // Create x => x.PropName.Count()
    PropertyInfo propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression arg1 = Expression.PropertyOrField(arg, propertyName);

    var countMethods = typeof(Queryable)
        .GetMethods()
        .Single(methodInfos => methodInfos.Name == "Count" && methodInfos.IsStatic &&
                                       methodInfos.GetParameters().Length == 1)
        .MakeGenericMethod(propertyInfo.PropertyType);

    Expression countCallExpression = Expression.Call(arg1, countMethods);

    // Get System.Linq.Queryable.OrderBy() method.
    Type enumarableType = typeof(Queryable);

    MethodInfo method = enumarableType
        .GetMethods()
        .Where(m => m.Name == orderMethod && m.IsGenericMethodDefinition)
        .Where(m =>
        {
            List<ParameterInfo> parameters = m.GetParameters().ToList();
            // Put more restriction here to ensure selecting the right overload that has 2 parameters
            return parameters.Count == 2;
        }).Single();

        if (propertyInfo == null)
        {
           throw new ArgumentException(nameof(propertyName));
        }

        //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
        MethodInfo genericMethod = method.MakeGenericMethod(entityType, propertyInfo.PropertyType);

        /* Call query.OrderBy(selector), with query and selector: x=> x.PropName
        Note that we pass the selector as Expression to the method and we don't compile it.
        By doing so EF can extract "order by" columns and generate SQL for it. */
        return (IOrderedQueryable<TSource>)genericMethod.Invoke(genericMethod, new object[] { query, countCallExpression });
}





Aucun commentaire:

Enregistrer un commentaire