Create LINQ to entities OrderBy expression on the fly

后端 未结 2 1606
予麋鹿
予麋鹿 2020-12-05 05:38

I\'m trying to add the orderby expression on the fly. But when the query below is executed I get the following exception:

System.NotSupportedException

2条回答
  •  借酒劲吻你
    2020-12-05 06:06

    You basically can't use query expressions like this, due to the way they're translated. However, you can do it explicitly with extension methods:

    string sortBy = HttpContext.Current.Request.QueryString["sidx"];
    ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting");
    Expression orderByProperty = Expression.Property(prm, sortBy);
    
    // get the paged records
    IQueryable query = be.buskerPosting
        .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
        .OrderBy(orderByExpression)
        .Select(posting => new PostingListItemDto { Set = posting })
        .Skip((page -   1) * pageSize)
        .Take(pageSize);
    

    The tricky bit is getting the right expression tree type - that'll come in an edit :)

    EDIT: The edit will be somewhat delayed for various reasons. Basically you may need to call a generic method using reflection, as Queryable.OrderBy needs a generic Expression> and although it looks like you know the source type at compile-time, you may not know the key type. If you do know it'll always be ordering by (say) an int, you can use:

    Expression orderByProperty = Expression.Property(prm, sortBy);
    var orderByExpression = Expression.Lambda>
        (orderByProperty, new[] { prm });
    

    EDIT: Okay, it looks like I had time after all. Here's a short example of calling OrderBy using reflection:

    using System;
    using System.Reflection;
    using System.Linq;
    using System.Linq.Expressions;
    
    public class Test
    {
        static void Main()
        {
            string[] names = { "Jon", "Holly", "Tom", "Robin", "Will" };
            var query = names.AsQueryable();
            query = CallOrderBy(query, "Length");
            foreach (var name in query)
            {
                Console.WriteLine(name);
            }
        }
    
        private static readonly MethodInfo OrderByMethod =
            typeof(Queryable).GetMethods()
                .Where(method => method.Name == "OrderBy")
                .Where(method => method.GetParameters().Length == 2)
                .Single();
    
        public static IQueryable CallOrderBy
            (IQueryable source, string propertyName)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
            Expression orderByProperty = Expression.Property(parameter, propertyName);
    
            LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
            Console.WriteLine(lambda);
            MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
                (new[] { typeof(TSource), orderByProperty.Type });
            object ret = genericMethod.Invoke(null, new object[] {source, lambda});
            return (IQueryable) ret;
        }
    }
    

    You could easily refactor CallOrderBy into an extension method (e.g. OrderByProperty) like this:

    public static class ReflectionQueryable
    {
        private static readonly MethodInfo OrderByMethod =
            typeof(Queryable).GetMethods()
                .Where(method => method.Name == "OrderBy")
                .Where(method => method.GetParameters().Length == 2)
                .Single();
    
        public static IQueryable OrderByProperty
            (this IQueryable source, string propertyName)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
            Expression orderByProperty = Expression.Property(parameter, propertyName);
    
            LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
            Console.WriteLine(lambda);
            MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
                (new[] { typeof(TSource), orderByProperty.Type });
            object ret = genericMethod.Invoke(null, new object[] {source, lambda});
            return (IQueryable) ret;
        }    
    }
    

    Your original code then becomes:

    string sortBy = HttpContext.Current.Request.QueryString["sidx"];
    // get the paged records
    IQueryable query = be.buskerPosting
        .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
        .OrderByProperty(sortBy)
        .Select(posting => new PostingListItemDto { Set = posting })
        .Skip((page -   1) * pageSize)
        .Take(pageSize);
    

    (Apologies for the formatting involving horizontal scrollbars... I'll reformat later if anyone cares. Or you could do it for me if you have enough rep ;)

提交回复
热议问题