Вопрос

I wanna create a sorting generic Func Creating Func<IQueryable<T>, IOrderedQueryable<T>> in an extension method :

public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
{
    //I expect the following result
    //Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.Name);

    //keyValuePair.Key is name of property in type of T that I should sort T by it
    switch (keyValuePair.Value)
    {
        case SortingType.Ascending:
            // Creating Ascending Sorting Func
            break;

        case SortingType.Descending:
            // Creating Descending Sorting Func
            break;

        default :
            break;
    }
}

Could you guide me, how I can do it?

Edit:

this sorting also, contains of count of a T's navigation property.
e.g:

// keyValuePair.Key equals "User.Count"
// User is a navigation property of T
Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.User.Count);

Edit:

I changed GetSelector as the following, but an exception has occurred in bodyExpression.

public static Expression GetSelector<T>(string propertyName)
{
    ParameterExpression parameter = Expression.Parameter(typeof(T));
    if (propertyName.Contains("."))
    {
        propertyName = propertyName.Substring(0, propertyName.IndexOf("."));
        Type navigationPropertyCollectionType = typeof(T).GetProperty(propertyName).PropertyType;

        if (navigationPropertyCollectionType.GetGenericTypeDefinition() == typeof(ICollection<>))
        {                        
            Expression countParameter = Expression.Parameter(navigationPropertyCollectionType, "c");
            MemberExpression countExpression = Expression.Property(countParameter, "Count");
//Exception: Instance property 'Users(ICollection`1)' is not defined for type 'System.Int32'
            var bodyExpression = Expression.Property(countExpression, propertyName, countParameter);
            return Expression.Lambda(bodyExpression, parameter);
        }
    }
    MemberExpression bodyMemberExpression = Expression.Property(parameter, typeof(T).GetProperty(propertyName));
    return Expression.Lambda(bodyMemberExpression, parameter);
} 
Это было полезно?

Решение

So the first thing that we'll need is a method that can get the selector expression that selects out that property value when given the property name. It will need to build the expression from scratch:

public static Tuple<Expression, Type> GetSelector<T>(IEnumerable<string> propertyNames)
{
    var parameter = Expression.Parameter(typeof(T));
    Expression body = parameter;

    foreach (var property in propertyNames)
    {
        body = Expression.Property(body,
            body.Type.GetProperty(property));
    }

    return Tuple.Create(Expression.Lambda(body, parameter) as Expression
        , body.Type);
}

Note that since this results in a chain of properties the method also returns the type of the final property, as that wouldn't be a particularly easy bit of information to access from the caller's perspective.

Because we don't know the return type of the property when calling Selector so we have no choice but to leave the return type of this method typed to Expression. We can't cast it to an Expression<Func<T, Something>>. We could have it return a Expression<Func<T, object>>, and that would work for all properties that select out a reference type, but this wouldn't be able to box value types, so it would throw a runtime exception in those cases.

Now, because we don't know the exact type of that expression we can't call OrderBy or OrderByDescending directly. We need to grab those methods through reflection and use MakeGenericMethod so that they can be created using the proper type based on inspection of that property using reflection.

public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(
    this Tuple<IEnumerable<string>, SortingType> sortCriteria)
{
    var selector = GetSelector<T>(sortCriteria.Item1);
    Type[] argumentTypes = new[] { typeof(T), selector.Item2 };

    var orderByMethod = typeof(Queryable).GetMethods()
        .First(method => method.Name == "OrderBy"
            && method.GetParameters().Count() == 2)
            .MakeGenericMethod(argumentTypes);
    var orderByDescMethod = typeof(Queryable).GetMethods()
        .First(method => method.Name == "OrderByDescending"
            && method.GetParameters().Count() == 2)
            .MakeGenericMethod(argumentTypes);

    if (sortCriteria.Item2 == SortingType.Descending)
        return query => (IOrderedQueryable<T>)
            orderByDescMethod.Invoke(null, new object[] { query, selector.Item1 });
    else
        return query => (IOrderedQueryable<T>)
            orderByMethod.Invoke(null, new object[] { query, selector.Item1 });
}

Другие советы

You would need to use Expressions

    public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
    {

        Func<IQueryable<T>, IOrderedQueryable<T>> result;

        var p1 = Expression.Parameter(typeof (T), "p1");
        var prop = Expression.PropertyOrField(p1, keyValuePair.Key);
        var lambada = Expression.Lambda<Func<T, object>>(prop, new ParameterExpression[] {p1});

        //keyValuePair.Key is name of property in type of T that I should sort T by it
        switch (keyValuePair.Value)
        {
            case SortingType.Ascending:
                result = source => source.OrderBy(lambada);
                break;

            case SortingType.Descending:
                result = source => source.OrderByDescending(lambada);
                break;

            default:
                throw new NotImplementedException();
                break;
        }

        return result;
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top