Search code examples
entity-frameworksystem.reflection

Entity framework ordering using reflection


I have an extension method for ordering, the sortExpression can be something like "Description" or "Description DESC", it is working perfectly for columns at the same table:

 public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        if (string.IsNullOrEmpty(sortExpression))
            return source;

        var parts = sortExpression.Split(' ');
        var isDescending = false;
        var propertyName = "";
        var type = typeof(T);

        if (parts.Length > 0 && parts[0] != "")
        {
            propertyName = parts[0];

            if (parts.Length > 1)
                isDescending = parts[1].ToLower().Contains("esc");

            var prop = type.GetProperty(propertyName);
            if (prop == null)
                throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, type.Name));

            var funcType = typeof(Func<,>)
                .MakeGenericType(type, prop.PropertyType);

            var lambdaBuilder = typeof(Expression)
                .GetMethods()
                .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
                .MakeGenericMethod(funcType);

            var parameter = Expression.Parameter(type);
            var propExpress = Expression.Property(parameter, prop);

            var sortLambda = lambdaBuilder
                .Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });

            var sorter = typeof(Queryable)
                .GetMethods()
                .FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
                .MakeGenericMethod(new[] { type, prop.PropertyType });

            var result = (IQueryable<T>)sorter
                .Invoke(null, new object[] { source, sortLambda });
            return result;
        }

        return source;
    }

Working Example:

    var query = db.Audit.Include("AccessLevel").AsQueryable();
    query = query.OrderBy("Description");

Please note that the "Description" column exists at the same table "Audit".

What I'm trying to do is to sort by a column in a relation table:

Like the following

  var query = db.Audit.Include("AccessLevel").AsQueryable();
  query = query.OrderBy("AccessLevel.Name");

Which is equivalent to:

query = query.OrderBy(o => o.AccessLevel.Name);

What is the required modification on my extension method ?


Solution

  • I solved it by using the following code:

     public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
            {
    
                    return ApplyOrder<T>(source, parts[0], "OrderBy");
    
            }
            public static IQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
            {
                return ApplyOrder<T>(source, property, "OrderByDescending");
            }
            public static IQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
            {
                return ApplyOrder<T>(source, property, "ThenBy");
            }
            public static IQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
            {
                return ApplyOrder<T>(source, property, "ThenByDescending");
            }
            static IQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
            {
    
                if (source == null)
                    throw new ArgumentNullException("source");
    
                if (string.IsNullOrEmpty(property))
                    return source;
    
    
                string[] props = property.Split('.');
                Type type = typeof(T);
                ParameterExpression arg = Expression.Parameter(type, "x");
                Expression expr = arg;
                foreach (string prop in props)
                {
                    // use reflection (not ComponentModel) to mirror LINQ
                    PropertyInfo pi = type.GetProperty(prop);
                    expr = Expression.Property(expr, pi);
                    type = pi.PropertyType;
                }
                Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
                LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
    
                object result = typeof(Queryable).GetMethods().Single(
                        method => method.Name == methodName
                                && method.IsGenericMethodDefinition
                                && method.GetGenericArguments().Length == 2
                                && method.GetParameters().Length == 2)
                        .MakeGenericMethod(typeof(T), type)
                        .Invoke(null, new object[] { source, lambda });
                return (IQueryable<T>)result;
            }