with orderbydescending orderbyascending ordenar lista c# linq generics linq-to-entities code-generation

c# - orderbydescending - Cómo verificar la presencia de un OrderBy en un árbol de expresiones ObjectQuery<T>



order by with linq c# (6)

La paginación depende de ordenar de una manera fuerte. ¿Por qué no unir estrechamente las operaciones? Aquí hay una manera de hacerlo:

Objetos de apoyo

public interface IOrderByExpression<T> { ApplyOrdering(ref IQueryable<T> query); } public class OrderByExpression<T, U> : IOrderByExpression<T> { public IQueryable<T> ApplyOrderBy(ref IQueryable<T> query) { query = query.OrderBy(exp); } //TODO OrderByDescending, ThenBy, ThenByDescending methods. private Expression<Func<T, U>> exp = null; //TODO bool descending? public OrderByExpression (Expression<Func<T, U>> myExpression) { exp = myExpression; } }

El método en discusión:

public IQueryable<Category> List(int startIndex, int count, IOrderByExpression<Category> ordering) { NorthwindEntities ent = new NorthwindEntities(); IQueryable<Category> query = ent.Categories; if (ordering == null) { ordering = new OrderByExpression<Category, int>(c => c.CategoryID) } ordering.ApplyOrdering(ref query); return query.Skip(startIndex).Take(count); }

Algún tiempo después, llamando al método:

var query = List(20, 20, new OrderByExpression<Category, string>(c => c.CategoryName));

Estoy usando T4 para generar repositorios para entidades LINQ to Entities.

El repositorio contiene (entre otras cosas) un método de lista adecuado para paginación. La documentación para Métodos soportados y no admitidos no lo menciona, pero no puede "invocar" Skip un IQueryable no ordenado. Levantará la siguiente excepción:

System.NotSupportedException: el método ''Omitir'' solo se admite para la entrada ordenada en LINQ to Entities. El método ''OrderBy'' debe invocarse antes del método ''Skip'' ...

Lo resolví permitiendo definir una clasificación por defecto a través de un método parcial. Pero estoy teniendo problemas para verificar si el árbol de expresiones realmente contiene un OrderBy .

Reduje el problema a un código tan bajo como sea posible:

public partial class Repository { partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery); public IQueryable<Category> List(int startIndex, int count) { IQueryable<Category> query = List(); ProvideDefaultSorting(ref query); if (!IsSorted(query)) { query = query.OrderBy(c => c.CategoryID); } return query.Skip(startIndex).Take(count); } public IQueryable<Category> List(string sortExpression, int startIndex, int count) { return List(sortExpression).Skip(startIndex).Take(count); } public IQueryable<Category> List(string sortExpression) { return AddSortingToTheExpressionTree(List(), sortExpression); } public IQueryable<Category> List() { NorthwindEntities ent = new NorthwindEntities(); return ent.Categories; } private Boolean IsSorted(IQueryable<Category> query) { return query is IOrderedQueryable<Category>; } } public partial class Repository { partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery) { currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting.. } }

¡Esta no es mi implementación real!

Pero mi pregunta es, ¿cómo podría implementar el método IsSorted ? El problema es que las consultas de LINQ to Entities siempre son del tipo ObjectQuery , que implementa IOrderedQueryable .

Entonces, ¿cómo debo asegurarme de que haya un método OrderBy en el árbol de expresiones? ¿Es la única opción para analizar el árbol?

Actualizar
ProvideDefaultSorting otras dos sobrecargas para dejar en claro que no se trata de cómo agregar compatibilidad de ordenamiento al repositorio, sino cómo verificar si el método parcial ProvideDefaultSorting realmente ha agregado un OrderBy al árbol de expresiones.

El problema es que la primera clase parcial se genera mediante una plantilla y la implementación de la segunda parte de la clase parcial la realiza un miembro del equipo en otro momento. Puede compararlo con la forma en que .NET Entity Framework genera EntityContext, permite puntos de extensión para otros desarrolladores. Por lo tanto, quiero intentar que sea robusto y no se bloquee cuando el ProvideDefaultSorting no se implemente correctamente.

Entonces, tal vez la pregunta es más, ¿cómo puedo confirmar que el ProvideDefaultSorting efectivamente agregó ordenar al árbol de expresiones?

Actualización 2
La nueva pregunta fue respondida, y aceptada, creo que debería cambiar el título para que coincida con la pregunta más. ¿O debería dejar el título actual porque llevará a personas con el mismo problema a esta solución?


Me temo que es un poco más difícil que eso. Como ve, Entity Framework, en determinadas circunstancias, ignorará silenciosamente un OrderBy. Por lo tanto, no es suficiente buscar un OrderBy en el árbol de expresiones. El OrderBy debe estar en el lugar "correcto", y la definición del lugar "correcto" es un detalle de implementación del Marco de la Entidad.

Como ya habrás adivinado, estoy en el mismo lugar que tú; Estoy usando el patrón de repositorio de entidades y haciendo un Take / Skip en la capa de presentación. La solución que he usado, que tal vez no es ideal, pero lo suficientemente buena para lo que estoy haciendo, es no hacer ningún pedido hasta el último momento posible, para garantizar que el OrderBy sea siempre lo último en el árbol de expresiones. Entonces, cualquier acción que vaya a hacer un Take / Skip (directa o indirectamente) inserta un OrderBy primero. El código está estructurado de manera que esto solo puede suceder una vez.


Puede abordar esto en el tipo de devolución de ProvideDefaultSorting. Este código no se compila:

public IOrderedQueryable<int> GetOrderedQueryable() { IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>(); return myInts.Where(i => i == 2); }

Este código se construye, pero es insidioso y el programador obtiene lo que se merece.

public IOrderedQueryable<int> GetOrderedQueryable() { IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>(); return myInts.Where(i => i == 2) as IOrderedQueryable<int>; }

La misma historia con ref (esto no se construye):

public void GetOrderedQueryable(ref IOrderedQueryable<int> query) { query = query.Where(i => i == 2); }


ProvideDefaultSorting(ref query); if (!IsSorted(query)) { query = query.OrderBy(c => c.CategoryID); }

Cambiar a:

//apply a default ordering query = query.OrderBy(c => c.CategoryID); //add to the ordering ProvideDefaultSorting(ref query);

No es una solución perfecta.

No resuelve el problema de "filtro en la función de ordenar" que ha indicado. Resuelve "Olvidé implementar pedidos" o "Elijo no ordenar".

Probé esta solución en LinqToSql:

public void OrderManyTimes() { DataClasses1DataContext myDC = new DataClasses1DataContext(); var query = myDC.Customers.OrderBy(c => c.Field3); query = query.OrderBy(c => c.Field2); query = query.OrderBy(c => c.Field1); Console.WriteLine(myDC.GetCommand(query).CommandText); }

Genera (observe el orden inverso de los pedidos):

SELECT Field1, Field2, Field3 FROM [dbo].[Customers] AS [t0] ORDER BY [t0].[Field1], [t0].[Field2], [t0].[Field3]


Gracias a David B tengo la siguiente solución. (Tuve que agregar detección para la situación donde el método parcial no se ejecutó o simplemente devolví su parámetro).

public partial class Repository { partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery); public IQueryable<Category> List(int startIndex, int count) { NorthwindEntities ent = new NorthwindEntities(); IOrderedQueryable<Category> query = ent.CategorySet; var oldQuery = query; ProvideDefaultSorting(ref query); if (oldQuery.Equals(query)) // the partial method did nothing with the query, or just didn''t exist { query = query.OrderBy(c => c.CategoryID); } return query.Skip(startIndex).Take(count); } // the rest.. } public partial class Repository { partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery) { currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")).OrderBy(c => c.CategoryName); // compile time forced sotring } }

Asegura en tiempo de compilación que si se implementa el método parcial, al menos debería mantenerlo como IOrderdQueryable.

Y cuando el método parcial no se implementa o simplemente devuelve su parámetro, la consulta no se cambiará, y usará la ordenación alternativa.