2013-05-21 19 views
10

Szukam sposobu na przechowywanie kolekcji Expression<Func<T, TProperty>> używanej do zamawiania elementów, a następnie do wykonania zapisanej listy na obiekcie IQueryable<T> (podstawowym dostawcą jest Entity Framework) .Lista wyrażeń <Func <T, TProperty >>

Na przykład, chciałbym zrobić coś takiego (to pseudokod):

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); 
    } 
} 

klauzuli ORDER BY (nieruchomość, na której można zamówić):

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; } 
    } 
} 

Repozytorium z moją metodą zapytania:

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(); 
    } 
} 

Moje pierwsze t pomysłem było przekonwertowanie Expression<Func<T, TProperty>> na ciąg znaków (nazwa właściwości do sortowania). Więc, zasadniczo, zamiast przechowywania wpisanej listy (która nie jest możliwa, ponieważ TProperty nie jest stała), przechowuję listę ciągów z właściwościami do sortowania.

Ale to nie działa, ponieważ wtedy nie mogę zrekonstruować Expression z powrotem (potrzebuję go, ponieważ IQueryable.OrderBy przyjmuje Expression<Func<T, TKey>> jako parametr).

Próbowałem również dynamicznie utworzyć wyrażenie (za pomocą Expression.Convert), aby uzyskać Expression<Func<T, object>>, ale dostałem wyjątek od struktury obiektu, który powiedział, że nie był w stanie obsłużyć instrukcji Expression.Convert.

Jeśli to możliwe, nie chcę korzystać z biblioteki zewnętrznej, takiej jak Dynamic Linq Library.

+0

BTW, twój kod nie zadziałałby, musisz wywołać 'OrderBy()' raz i użyć 'ThenBy()' dla kolejnych wywołań. – svick

+0

Jak powiedziałem w moim pytaniu, to był po prostu pseudo kod ... W rzeczywistości miałem już rozwiązanie mojego problemu, ale z wykorzystaniem Dynamic Linq Library, którego chciałem uniknąć. Tak więc problem z zamówieniem, o którym wspomniałeś, został już rozwiązany :), ale i tak dziękuję! – Bidou

Odpowiedz

10

Jest to jeden z nielicznych przypadków, w których odpowiednie może być rozwiązanie z odbiciem dynamic.

Myślę, że chcesz coś takiego? (Czytałem między wierszami i wprowadziłem pewne zmiany w twojej strukturze, gdzie uważałem to za konieczne).

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(); 
    } 
} 

Kluczem Sztuką jest uzyskanie C# dynamic zrobić rozdzielczość przeciążeniem straszne i typu wnioskowanie do nas. Co więcej, wierzę, że powyższe, pomimo użycia dynamic, jest w rzeczywistości bezpieczne!

2

Możesz przechowywać wyrażenia lambda w kolekcji jako instancje typu LambdaExpression.

A nawet lepiej, przechowuj definicje sortowania, z których każda, oprócz wyrażenia, otwiera również kierunek sortowania.

Załóżmy, że mamy następującą metodę rozszerzenia

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; 
} 

i podobną metodę ThenBy.Następnie można zrobić coś takiego

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

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

Oto definicje SortDefinition i 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

To działa świetnie, dzięki! Jednak agregacja prawdopodobnie nie zadziała (nie przetestowałem jej), ponieważ musisz wywołać funkcję ThenBy/ThenByDescending, jeśli chcesz wykonać wiele sortowania ... – Bidou

+0

Coś innego: aby wykonać zapytanie, użyj dynamicznego keywork zgodnie z propozycją Ani jest łatwiejsze. Zasadniczo zastępuje twoją klasę MethodHelper. Ale dzięki za pomocną odpowiedź! – Bidou

+0

@Bidou: Przetestowałem to i stwierdziłem, że agregacja z 'OrderBy' nie działa. Dokona edycji odpowiedzi. – Gebb

3

Jednym ze sposobów, aby to zrobić byłoby „Store” wszystkie klauzule sortowania w czymś takim Func<IQueryable<T>, IOrderedQueryable<T>> (czyli , funkcja, która zwraca metody sortowania):

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); 
    } 
} 

ten sposób, nie mają do czynienia z odbiciem lub dynamic, cały ten kod jest bezpieczny i stosunkowo łatwy do zrozumienia.

+0

Tak, to prawda, to całkiem proste, ale problem z tym rozwiązaniem polega na tym, że potrzebujesz bezpośredniego dostępu do IQueryable. Moja lista OrderBy ma być używana przez GUI (w zasadzie do abstrakcji zapytania), więc nie chcę mieć tutaj obiektu Queryable ... Wolałbym zarządzać nim w Logice biznesowej! – Bidou

+0

@Bidou Naprawdę nie rozumiem, co próbujesz powiedzieć. Jeśli chcesz zachować 'Repozytorium' jak w swoim pseudo kodzie, po prostu zmień jego kod, aby wziąć całość' OrderClause', a następnie wykonaj 'return orderBys.Order (_query) .ToList();'. – svick

+1

+1: To naprawdę fajne rozwiązanie i, jak sam mówisz, starannie unika uników dynamiki/odbicia. – Ani

Powiązane problemy