2012-05-07 14 views
5

Estoy tratando de crear una clase rápida para que pueda hacer que el código de ordenación de escritura para una grilla sea mucho más fácil de trabajar y mantener, y para mantener baja la repetición de código. Para ello se me ocurrió con la clase siguiente:¿Cómo puedo almacenar dinámicamente expresiones usadas para orden de Linq?

public class SortConfig<TSource, TRelatedObject> where TSource : class where TRelatedObject : class 
{ 
    public IList<SortOption> Options { get; protected set; } 
    public SortOption DefaultOption { get; set; } 

    public SortConfig() 
    { 
     Options = new List<SortOption>(); 
    } 

    public void Add(string name, Expression<Func<TSource, object>> sortExpression, TRelatedObject relatedObject, bool isDefault = false) 
    { 
     var option = new SortOption 
     { 
      FriendlyName = name, 
      SortExpression = sortExpression, 
      RelatedObject = relatedObject 
     }; 

     Options.Add(option); 

     if (isDefault) 
      DefaultOption = option; 
    } 

    public SortOption GetSortOption(string sortName) 
    { 
     if (sortName.EndsWith("asc", StringComparison.OrdinalIgnoreCase)) 
      sortName = sortName.Substring(0, sortName.LastIndexOf("asc", StringComparison.OrdinalIgnoreCase)); 
     else if (sortName.EndsWith("desc", StringComparison.OrdinalIgnoreCase)) 
      sortName = sortName.Substring(0, sortName.LastIndexOf("desc", StringComparison.OrdinalIgnoreCase)); 

     sortName = sortName.Trim(); 

     var option = Options.Where(x => x.FriendlyName.Trim().Equals(sortName, StringComparison.OrdinalIgnoreCase)) 
          .FirstOrDefault(); 
     if (option == null) 
     { 
      if (DefaultOption == null) 
       throw new InvalidOperationException(
        string.Format("No configuration found for sort type of '{0}', and no default sort configuration exists", sortName)); 

      option = DefaultOption; 
     } 

     return option; 
    } 

    public class SortOption 
    { 
     public string FriendlyName { get; set; } 
     public Expression<Func<TSource, object>> SortExpression { get; set; } 
     public TRelatedObject RelatedObject { get; set; } 
    } 
} 

La idea es que se crea una configuración rápida de las diferentes opciones de clasificación, lo que orderBy expresión se utiliza para eso, y opcionalmente un objeto que está relacionada con la opción de clasificación Esto permite que el código para que parezca:

protected void InitSortConfig() 
    { 
     _sortConfig = new SortConfig<xosPodOptimizedSearch, HtmlAnchor>(); 
     _sortConfig.Add("name", (x => x.LastName), lnkSortName, true); 
     _sortConfig.Add("team", (x => x.SchoolName), lnkSortTeam); 
     _sortConfig.Add("rate", (x => x.XosRating), lnkSortRate); 
     _sortConfig.Add("pos", (x => x.ProjectedPositions), null); 
     _sortConfig.Add("height", (x => x.Height), lnkSortHeight); 
     _sortConfig.Add("weight", (x => x.Weight), lnkSortWeight); 
     _sortConfig.Add("city", (x => x.SchoolCity), lnkSortCity); 
     _sortConfig.Add("state", (x => x.SchoolState), lnkSortState); 
    } 

y luego me puede ordenar con sólo hacer

 // Get desired sorting configuration 
     InitSortConfig(); 
     var sortOption = _sortConfig.GetSortOption(sort); 
     bool isDescendingSort = sort.EndsWith("desc", StringComparison.OrdinalIgnoreCase); 

     // Setup columns 
     InitSortLinks(); 
     if (sortOption.RelatedObject != null) 
     { 
      // Make modifications to html anchor 
     } 

     // Form query 
     var query = PodDataContext.xosPodOptimizedSearches.AsQueryable(); 

     if (isDescendingSort) 
      query = query.OrderByDescending(sortOption.SortExpression); 
     else 
      query = query.OrderBy(sortOption.SortExpression); 

Esto funciona muy bien cuando la variable ordenada es una cadena, pero cuando no es una cadena consigo la siguiente excepción: Cannot order by type 'System.Object'.

Supongo que esto se debe a que estoy almacenando la expresión como Expression<Func<TSource, object>> y no siendo más específico acerca de ese segundo genérico. No entiendo cómo puedo mantener todas mis opciones de clasificación diferentes (incluso para propiedades que no sean cadenas) en una clase.

Creo que uno de los problemas es que la cláusula Linq.OrderBy() toma Expression<Func<TSource, TKey>> ya que es el parámetro, pero no estoy envolviendo la cabeza en torno a cómo Linq.OrderBy() es capaz de inferir lo TKey debería ser, y por lo tanto no puede entender cómo tomar ventaja de esa inferencia para almacenar estas expresiones con el TKey apropiado.

¿Alguna idea?

+0

Tome un vistazo a [esta] (http://weblogs.asp.net/scottgu/archive/2008/01 /07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx) – Nestor

+0

Gracias, pero prefiero que la clasificación esté especificada por el código en lugar de la cadena especificada, especialmente porque la estructura de datos de mi grid no es un modelo de datos 1: 1 con mi base de datos, por lo que la ordenación debe traducirse de la grilla especificada a una ordenación de base de datos de todos modos – KallDrexx

+0

¿No está usando una cadena mágica cuando obtiene la opción de ordenar de todos modos? –

Respuesta

5

El argumento genérico se infiere de este modo:

IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> enumerable, Func<TSource, TKey> expression) 

Cuando tenga una IEnumerable<T>, el compilador es capaz de inferir que la TSource es T en esta situación debido a la declaración de método de extensión; por lo que el método de extensión ya se agregó al saber qué es TSource. Por ejemplo:

Enumerable.Range(0, 10).OrderBy(x => x) 

Desde que empezamos con un IEnumerable<int>, el compilador puede inferir que la expresión que espera es Func<int, TKey>, porque la extensión está afectando a la IEnumerable<int>. A continuación, como su expresión devuelve un valor, el compilador puede inferir el tipo restante, en esta situación int, por lo que se convierte en Func<int, int> con este ejemplo.

Ahora, en relación con su problema particular, puede configurar fácilmente su expresión para que funcione correctamente si geniza su objeto SortConfig adecuadamente. Parece que tu SortConfig tiene un delegado Func<TSource, object> en este momento. Si genérico su SortConfig para usar otro tipo, gana especificidad. Ejemplo:

void Add<TSource, TKey>(string name, Func<TSource, TKey> expression) 

El siguiente problema aquí es cómo almacenar sus métodos de respaldo en algún formato. Y su declaración de la clase se ve así:

public class SortConfig<TSource> 

A continuación, todos los tipos de datos deben estar alineados cuando se invoca la extensión OrderBy.

EDIT: Aquí está un ejemplo práctico de lo que creo que quiere hacer:

static void Main(string[] args) 
    { 
     var list = Enumerable.Range(0, 10).Reverse().Select(x => new SampleClass { IntProperty = x, StringProperty = x + "String", DateTimeProperty = DateTime.Now.AddDays(x * -1) }); 

     SortContainer<SampleClass> container = new SortContainer<SampleClass>(); 
     container.Add("Int", x => x.IntProperty); 
     container.Add("String", x => x.StringProperty); 
     container.Add("DateTime", x => x.DateTimeProperty); 

     var sorter = container.GetSorterFor("Int"); 

     sorter.Sort(list).ForEach(x => Console.WriteLine(x.IntProperty)); 
     Console.ReadKey(); 
    } 

    public class SampleClass 
    { 
     public int IntProperty { get; set; } 
     public string StringProperty { get; set; } 
     public DateTime DateTimeProperty { get; set; } 
    } 

    public class SortContainer<TSource> 
    { 
     protected Dictionary<string, ISorter<TSource>> _sortTypes = new Dictionary<string, ISorter<TSource>>(); 

     public void Add<TKey>(string name, Func<TSource, TKey> sortExpression) 
     { 
      Sorter<TSource, TKey> sorter = new Sorter<TSource, TKey>(sortExpression); 
      _sortTypes.Add(name, sorter); 
     } 

     public ISorter<TSource> GetSorterFor(string name) 
     { 
      return _sortTypes[name]; 
     } 
    } 

    public class Sorter<TSource, TKey> : ISorter<TSource> 
    { 
     protected Func<TSource, TKey> _sortExpression = null; 

     public Sorter(Func<TSource, TKey> sortExpression) 
     { 
      _sortExpression = sortExpression; 
     } 

     public IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable) 
     { 
      return sourceEnumerable.OrderBy(_sortExpression); 
     } 
    } 

    public interface ISorter<TSource> 
    { 
     IOrderedEnumerable<TSource> Sort(IEnumerable<TSource> sourceEnumerable); 
    } 
+0

De alguna manera llego a donde vas con esto, pero ¿de qué se trata ese método 'void'' Add'? ¿Te refieres a '_sortConfig.Add'? –

+0

Sí, en realidad no sabía cuál es la firma de tu método, así que tuve que asumirlo. – Tejs

+0

Eso tiene sentido, excepto que no estoy seguro de qué tipo dar a la propiedad 'SortOption.SortExpression' para almacenar mis expresiones – KallDrexx

Cuestiones relacionadas