Создание LINQ для сущностей OrderBy выражение на лету
Я пытаюсь добавить выражение orderby на лету. Но когда запрос ниже выполняется, я получаю следующее исключение:
Система.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 ответов:
Вы в принципе не можете использовать выражения запросов, подобные этому, из-за способа их перевода. Однако вы можете сделать это явно с помощью методов расширения:
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);Хитрость заключается в том, чтобы получить правильный тип дерева выражений-это будет в редактировании:)
EDIT: редактирование будет несколько отложено по разным причинам. В принципе, вам может потребоваться вызвать универсальный метод с помощью отражения, так как
Queryable.OrderByнуждается в универсальномExpression<Func<TSource, TKey>>, и хотя похоже, что вы знаете тип источника во время компиляции вы можете не знать тип ключа. Если вы знаете , что он всегда будет упорядочиваться (скажем) int, вы можете использовать:Expression orderByProperty = Expression.Property(prm, sortBy); var orderByExpression = Expression.Lambda<Func<buskerPosting, int>> (orderByProperty, new[] { prm });EDIT: ладно, похоже, у меня все-таки было время. Вот краткий пример вызова
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);(приношу извинения за форматирование, включающее горизонтальная полоса прокрутки... Я переформатируюсь позже, если кому-то будет интересно. Или вы могли бы сделать это для меня, если у вас достаточно репутации ;)
Я хотел поделиться своей реализацией, используя ответ Джона выше в качестве отправной точки. В этом случае вместо сортировки по имени строкового свойства, поступающего из уровня представления (поскольку название этого вопроса не является конкретным), я создаю слой данных 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, чтобы рекурсивно удалить вызовы преобразования из 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; }Я надеюсь, что это полезно! Спасибо, Джон!
Comments