2013-05-21 15 views
10

Sto cercando un modo per memorizzare una raccolta di Expression<Func<T, TProperty>> utilizzata per ordinare elementi e quindi per eseguire l'elenco memorizzato su un oggetto IQueryable<T> (il provider sottostante è Entity Framework) .Elenco di espressioni <Func <T, TProperty >>

Per esempio, mi piacerebbe fare qualcosa di simile (questo è pseudo codice):

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     OrderClause<User> orderBys = new OrderClause<User>(); 
     orderBys.AddOrderBy(u => u.Firstname); 
     orderBys.AddOrderBy(u => u.Lastname); 
     orderBys.AddOrderBy(u => u.Age); 

     Repository<User> userRepository = new Repository<User>(); 
     IEnumerable<User> result = userRepository.Query(orderBys.OrderByClauses); 
    } 
} 

una clausola ORDER BY (proprietà su cui ordinare):

public class OrderClause<T> 
{ 
    public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector) 
    { 
     _list.Add(orderBySelector); 
    } 

    public IEnumerable<Expression<Func<T, ???>>> OrderByClauses 
    { 
     get { return _list; } 
    } 
} 

Un repository con il mio metodo di query:

public class Repository<T> 
{ 
    public IEnumerable<T> Query(IEnumerable<OrderClause<T>> clauses) 
    { 
     foreach (OrderClause<T, ???> clause in clauses) 
     { 
      _query = _query.OrderBy(clause); 
     } 

     return _query.ToList(); 
    } 
} 

I miei abeti L'idea era di convertire lo Expression<Func<T, TProperty>> in una stringa (il nome della proprietà su cui ordinare). Quindi, in pratica, invece di memorizzare una lista tipizzata (che non è possibile perché TProperty non è costante), memorizzo una lista di stringhe con le proprietà su cui ordinare.

Ma questo non funziona perché poi non riesco a ricostruire la Expression posteriore (ne ho bisogno perché IQueryable.OrderBy vuole un Expression<Func<T, TKey>> come parametro).

Ho anche provato a creare dinamicamente l'espressione (con l'aiuto di Expression.Convert), per avere un Expression<Func<T, object>> ma poi ho ottenuto un'eccezione dal framework di entità che diceva che non era in grado di gestire l'istruzione Expression.Convert.

Se possibile, non voglio utilizzare una libreria esterna come Dynamic Linq Library.

+0

BTW, il tuo codice non funzionerà, dovrai chiamare 'OrderBy()' una volta e usare 'ThenBy()' per le chiamate successive. – svick

+0

Come ho affermato nella mia domanda, questo era solo uno pseudo codice ... In effetti, avevo già una soluzione al mio problema, ma con l'uso della Dynamic Linq Library che volevo evitare. Quindi il problema degli ordini che hai menzionato era già risolto :), ma grazie comunque! – Bidou

risposta

10

Questo è uno dei pochi casi in cui una soluzione di riflessione dynamic/reflection può essere appropriata.

Penso che tu voglia qualcosa del genere? (Ho letto tra le righe e ho apportato alcune modifiche alla tua struttura dove pensavo fosse necessario).

public class OrderClauseList<T> 
{ 
    private readonly List<LambdaExpression> _list = new List<LambdaExpression>(); 

    public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector) 
    { 
     _list.Add(orderBySelector); 
    } 

    public IEnumerable<LambdaExpression> OrderByClauses 
    { 
     get { return _list; } 
    } 
} 

public class Repository<T> 
{ 
    private IQueryable<T> _source = ... // Don't know how this works 

    public IEnumerable<T> Query(OrderClause<T> clauseList) 
    { 
     // Needs validation, e.g. null-reference or empty clause-list. 

     var clauses = clauseList.OrderByClauses; 

     IOrderedQueryable<T> result = Queryable.OrderBy(_source, 
                 (dynamic)clauses.First()); 

     foreach (var clause in clauses.Skip(1)) 
     { 
      result = Queryable.ThenBy(result, (dynamic)clause); 
     } 

     return result.ToList(); 
    } 
} 

Il trucco chiave sta ottenendo C# dynamic a fare la risoluzione di sovraccarico orribile e tipo di inferenza per noi. Per di più, credo che quanto sopra, nonostante l'uso di dynamic, sia effettivamente sicuro dal punto di vista del tipo!

2

È possibile memorizzare le espressioni lambda in una raccolta come istanze del tipo LambdaExpression.

O meglio ancora, memorizza le definizioni di ordinamento, ognuna delle quali, oltre a un'espressione, memorizza anche una direzione di ordinamento.

Si supponga di avere il seguente metodo di estensione

public static IQueryable<T> OrderBy<T>(
    this IQueryable<T> source, 
    SortDefinition sortDefinition) where T : class 
{ 
    MethodInfo method; 
    Type sortKeyType = sortDefinition.Expression.ReturnType; 
    if (sortDefinition.Direction == SortDirection.Ascending) 
    { 
     method = MethodHelper.OrderBy.MakeGenericMethod(
      typeof(T), 
      sortKeyType); 
    } 
    else 
    { 
     method = MethodHelper.OrderByDescending.MakeGenericMethod(
      typeof(T), 
      sortKeyType); 
    } 

    var result = (IQueryable<T>)method.Invoke(
     null, 
     new object[] { source, sortDefinition.Expression }); 
    return result; 
} 

e un metodo simile per ThenBy.Poi si può fare qualcosa di simile

myQueryable = myQueryable.OrderBy(sortDefinitions.First()); 

myQueryable = sortDefinitions.Skip(1).Aggregate(
    myQueryable, 
    (current, sortDefinition) => current.ThenBy(sortDefinition)); 

Qui ci sono le definizioni di SortDefinition e MethodHelper

public class SortDefinition 
{ 
    public SortDirection Direction 
    { 
     get; 
     set; 
    } 

    public LambdaExpression Expression 
    { 
     get; 
     set; 
    } 
} 

internal static class MethodHelper 
{ 
    static MethodHelper() 
    { 
     OrderBy = GetOrderByMethod(); 
     ThenBy = GetThenByMethod(); 
     OrderByDescending = GetOrderByDescendingMethod(); 
     ThenByDescending = GetThenByDescendingMethod(); 
    } 

    public static MethodInfo OrderBy 
    { 
     get; 
     private set; 
    } 

    public static MethodInfo ThenBy 
    { 
     get; 
     private set; 
    } 

    public static MethodInfo OrderByDescending 
    { 
     get; 
     private set; 
    } 

    public static MethodInfo ThenByDescending 
    { 
     get; 
     private set; 
    } 

    private static MethodInfo GetOrderByMethod() 
    { 
     Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr = 
      q => q.OrderBy((Expression<Func<object, object>>)null); 

     return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition(); 
    } 

    private static MethodInfo GetThenByMethod() 
    { 
     Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr = 
      q => q.ThenBy((Expression<Func<object, object>>)null); 

     return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition(); 
    } 

    private static MethodInfo GetOrderByDescendingMethod() 
    { 
     Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr = 
      q => q.OrderByDescending((Expression<Func<object, object>>)null); 

     return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition(); 
    } 

    private static MethodInfo GetThenByDescendingMethod() 
    { 
     Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr = 
      q => q.ThenByDescending((Expression<Func<object, object>>)null); 

     return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition(); 
    } 
} 
+0

Che funziona benissimo, grazie! Tuttavia, l'aggregazione probabilmente non funzionerà (non l'ho testata) dal momento che è necessario chiamare ThenBy/ThenByDescending se si desidera eseguire più ordinamenti ... – Bidou

+0

Qualcos'altro: fare la query, utilizzando il keywork dinamico come proposto da Ani è più facile. Fondamentalmente, sostituisce la tua classe MethodHelper. Ma grazie comunque per la tua risposta utile! – Bidou

+0

@Bidou: l'ho provato e ho trovato che l'aggregazione con 'OrderBy' non funziona. Modifica la risposta. – Gebb

3

Un modo per farlo sarebbe quello di “store” tutte le clausole di ordinamento in qualcosa di simile Func<IQueryable<T>, IOrderedQueryable<T>> (cioè , una funzione che chiama i metodi di ordinamento):

public class OrderClause<T> 
{ 
    private Func<IQueryable<T>, IOrderedQueryable<T>> m_orderingFunction; 

    public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector) 
    { 
     if (m_orderingFunction == null) 
     { 
      m_orderingFunction = q => q.OrderBy(orderBySelector); 
     } 
     else 
     { 
      // required so that m_orderingFunction doesn't reference itself 
      var orderingFunction = m_orderingFunction; 
      m_orderingFunction = q => orderingFunction(q).ThenBy(orderBySelector); 
     } 
    } 

    public IQueryable<T> Order(IQueryable<T> source) 
    { 
     if (m_orderingFunction == null) 
      return source; 

     return m_orderingFunction(source); 
    } 
} 

In questo modo, non hanno a che fare con la riflessione o dynamic, tutto questo codice è di tipo sicuro e relativamente facile da capire.

+0

Sì, è vero, è abbastanza semplice, ma il problema con questa soluzione è che è necessario un accesso diretto a IQueryable. La mia lista di OrderBy è pensata per essere utilizzata dalla GUI (fondamentalmente per astrarre la query), quindi non voglio avere un oggetto Queryable qui ... Preferirei gestirlo nella Business Logic! – Bidou

+0

@Bidou davvero non capisco cosa stai cercando di dire. Se vuoi mantenere il 'Repository' come nel tuo pseudo-codice, basta cambiare il suo codice per prendere l'intero' OrderClause' e ​​poi fare 'return orderBys.Order (_query) .ToList();'. – svick

+1

+1: Questa è una soluzione davvero bella e come dici tu, evita ordinatamente dinamica/riflessione. – Ani

Problemi correlati