Создать LINQ для сущностей OrderBy выражение на лету - PullRequest
16 голосов
/ 16 мая 2010

Я пытаюсь добавить выражение orderby на лету.Но когда выполняется приведенный ниже запрос, я получаю следующее исключение:

System.NotSupportedException: невозможно создать постоянное значение типа «тип закрытия».В этом контексте поддерживаются только примитивные типы (такие как Int32, String и Guid).

Странно то, что я запрашиваю только те примитивные типы.

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<PostingListItemDto> query =
   (from posting in be.buskerPosting
    where posting.buskerAccount.cmsMember.nodeId == m.Id
    orderby orderByProperty
    //orderby posting.Created 
    select new PostingListItemDto { Set = posting }).Skip<PostingListItemDto>((page -   1) * pageSize).Take<PostingListItemDto>(pageSize);

Надеюсь, кто-нибудь может пролить свет на это!

Ответы [ 2 ]

44 голосов
/ 16 мая 2010

Вы не можете использовать подобные выражения запросов из-за способа их перевода. Однако вы можете сделать это явно с помощью методов расширения:

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<PostingListItemDto> query = be.buskerPosting
    .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
    .OrderBy(orderByExpression)
    .Select(posting => new PostingListItemDto { Set = posting })
    .Skip<PostingListItemDto>((page -   1) * pageSize)
    .Take<PostingListItemDto>(pageSize);

Хитрость заключается в том, чтобы получить правильный тип дерева выражений - это придет в редакцию:)

РЕДАКТИРОВАТЬ: редактирование будет несколько отложено по различным причинам. В основном вам может потребоваться вызов универсального метода с использованием отражения, поскольку Queryable.OrderBy требуется универсальный Expression<Func<TSource, TKey>>, и, хотя кажется, что вы знаете тип source во время компиляции, вы можете не знать тип ключа , Если вы делаете знаете, что он всегда будет заказывать, скажем, по int, вы можете использовать:

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

РЕДАКТИРОВАТЬ: Хорошо, похоже, у меня было время, в конце концов. Вот краткий пример вызова OrderBy с использованием отражения:

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<TSource> CallOrderBy<TSource>
        (IQueryable<TSource> 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<TSource>) ret;
    }
}

Вы можете легко преобразовать CallOrderBy в метод расширения (например, OrderByProperty), например:

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<TSource> OrderByProperty<TSource>
        (this IQueryable<TSource> 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<TSource>) ret;
    }    
}

Ваш оригинальный код становится:

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

(Прошу прощения за форматирование с использованием горизонтальных полос прокрутки ... Я переформатирую позже, если кому-нибудь захочется. Или вы могли бы сделать это для меня, если у вас достаточно повторений;)

0 голосов
/ 05 декабря 2017

Я хотел бы поделиться своей реализацией, используя ответ Джона выше в качестве отправной точки. В этом случае, вместо того, чтобы сортировать по имени свойства строки, полученному из уровня представления (так как заголовок этого вопроса не является определенным в этом отношении), я строю слой данных Entity Framework и хочу, чтобы его потребитель указал порядок по свойствам как лямбда-выражения. И.Е. Вместо того, чтобы передать "sidx", я хотел иметь возможность использовать p => p.sidx. Я также хотел иметь возможность передавать неограниченное количество заказов по свойствам и указывать возрастающий или убывающий порядок.

Ну, мой метод может принять такое лямбда-выражение как тип Expression<Func<T, object>>. Это позвольте мне назвать это так, как я хочу, но проблема в том, что Entity Framework не может перевести выражение в SQL, если второй обобщенный параметр не является строго типизированным. Для метода расширения OrderBy требуются два общих параметра: T - тип, к которому принадлежит свойство, и TKey - тип, который возвращает свойство. Итак, первым шагом было изменение примера Джона для преобразования данного Expression<Func<T, object>> в Expression<Func<T, Tkey>> (как только мы работаем в контексте запроса, мы можем определить тип TKey):

internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, Expression<Func<T, object>> sortExp)
{
    //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
    //in order for Entity Framework to be able to translate it to SQL
    MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExp.Body) as MemberExpression;
    ParameterExpression sourceParam = sortExp.Parameters[0];

    LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

    MethodInfo orderByMethod = OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

    //Call OrderBy or OrderByDescending on the source IQueryable<T>
    return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
}

Как я уже говорил, я хочу принимать неограниченное количество заказов по ключевым селекторам, а также иметь возможность указывать восходящее или нисходящее направление, поэтому я создал класс-оболочку для Expression<Func<T, object>>, который я назвал DynamicSortExpression:

public class DynamicSortExpression<T>
{
    /// <summary>
    /// Creates a new ascending DynamicSortExpression 
    /// </summary>
    /// <param name="keySelector">A MemberExpression identifying the property to sort on</param>
    public DynamicSortExpression(Expression<Func<T, object>> keySelector) : this(keySelector, false)
    {
    }

    public DynamicSortExpression(Expression<Func<T, object>> keySelector, bool descending)
    {
        this.KeySelector = keySelector;
        this.Desc = descending;
    }

    /// <summary>
    /// Gets the expression that selects the property of T to sort on
    /// </summary>
    public Expression<Func<T, object>> KeySelector { get; }

    /// <summary>
    /// Gets sort expression is in ascending or descending order
    /// </summary>
    public bool Desc { get; }
}

Затем я обновил метод расширения, чтобы принять этот тип, и создал перегрузку для OrderBy, которая получает List<DynamicSortExpression<T>> и добавляет их в запрос, используя методы расширения один за другим. Вот окончательный результат:

public static class Extensions
{
    private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods()
                                                        .Where(method => method.Name == "OrderBy")
                                                        .Where(method => method.GetParameters().Length == 2)
                                                        .Single();

    private static readonly MethodInfo OrderByDescMethod = typeof(Queryable).GetMethods()
                                                            .Where(method => method.Name == "OrderByDescending")
                                                            .Where(method => method.GetParameters().Length == 2)
                                                            .Single();

    private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods()
                                                    .Where(method => method.Name == "ThenBy")
                                                    .Where(method => method.GetParameters().Length == 2)
                                                    .Single();

    private static readonly MethodInfo ThenByDescMethod = typeof(Queryable).GetMethods()
                                                    .Where(method => method.Name == "ThenByDescending")
                                                    .Where(method => method.GetParameters().Length == 2)
                                                    .Single();

    internal static IQueryable<T> OrderBy<T>(this IQueryable<T> sourceQuery, List<DynamicSortExpression<T>> orderBy)
    {
        bool isFirst = true;
        foreach (var sortExpression in orderBy)
        {
            if (isFirst)
            {
                sourceQuery = sourceQuery.OrderByDynamic(sortExpression);
                isFirst = false;
            }
            else
                sourceQuery = sourceQuery.ThenByDynamic(sortExpression);
        }

        return sourceQuery;
    }

    internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
    {
        //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
        //in order for Entity Framework to be able to translate it to SQL
        MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
        ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];

        LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

        MethodInfo orderByMethod = sortExpression.Desc ?
                                        OrderByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
                                        OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

        //Call OrderBy or OrderByDescending on the source IQueryable<T>
        return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
    }

    internal static IQueryable<T> ThenByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
    {
        //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
        //in order for Entity Framework to be able to translate it to SQL
        Expression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
        ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];

        LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

        MethodInfo orderByMethod = sortExpression.Desc ?
                                        ThenByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
                                        ThenByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

        //Call OrderBy or OrderByDescending on the source IQueryable<T>
        return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
    }
}

Теперь мой слой данных может иметь метод, подобный List<T> GetList(Expression<Func<T, bool>> where, params DynamicSortExpression<T>[] orderBy), который может быть вызван как

new MyClass<Person>().GetList(p => p.FirstName == "Billy", //where clause
                              new DynamicSortExpression<T>(p => p.FirstName),
                              new DynamicSortExpression<T>(p => p.LastName, true));

Метод RemoveConvert - это то, что я вырвал из исходного кода EntityFramework для рекурсивного удаления вызовов Convert из MemberExpression:

internal static Expression RemoveConvert(Expression expression)
{
    System.Diagnostics.Debug.Assert(expression != null);

    while ((expression != null)
            && (expression.NodeType == ExpressionType.Convert
                || expression.NodeType == ExpressionType.ConvertChecked))
    {
        expression = RemoveConvert(((UnaryExpression)expression).Operand);
    }

    return expression;
}

Надеюсь, это полезно! Спасибо Джон!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...