2008-10-22 7 views
6

Używam T4 do generowania repozytoriów dla LINQ do jednostek Entities.Jak sprawdzić obecność OrderBy w drzewie wyrażenia ObjectQuery <T>

Repozytorium zawiera (między innymi) metodę listy odpowiednią do stronicowania. Dokumentacja dla Supported and Unsupported Methods nie wspomina o tym, ale nie można "zadzwonić" pod numer Skip po nieuporządkowanym IQueryable. Podniesie następujący wyjątek:

System.NotSupportedException: metoda „skip” jest obsługiwana tylko dla posortowane wejścia w LINQ podmiotom. Metoda „OrderBy” musi zostać wywołana przed metoda „Pomiń” ..

Rozwiązałem go, pozwalając na zdefiniowanie domyślnego sortowania poprzez częściowe metody. Ale mam problem ze sprawdzeniem, czy drzewo wyrażeń rzeczywiście zawiera OrderBy.

mam zmniejszona problem jako mniej kodu, jak to możliwe:

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

To nie jest moja prawdziwa realizacja!

Ale moje pytanie pytanie jest, jak mogę wdrożyć metodę IsSorted? Problem polega na tym, że zapytania LINQ do encji są zawsze typu ObjectQuery, który implementuje IOrderedQueryable.

Jak zatem upewnić się, że w drzewie wyrażeń jest dostępna metoda OrderBy? Czy jest jedyną opcją parsowania drzewa?

Aktualizacja
Dodałem dwa inne przeciążeń, by wyjaśnić, że nie chodzi o to, jak dodać obsługę sortowania do repozytorium, ale jak sprawdzić, czy częściowa metoda ProvideDefaultSorting rzeczywiście dodał OrderBy do drzewa wyrażenie .

Problem polega na tym, że pierwsza klasa cząstkowa jest generowana przez szablon, a implementacja drugiej części klasy częściowej jest dokonywana przez członka zespołu w innym czasie. Można to porównać ze sposobem, w jaki struktura .NET Entity Framework generuje EntityContext, umożliwiając punkty rozszerzeń innym programistom. Dlatego chcę, aby była solidna i nie uległa awarii, gdy ProvideDefaultSorting nie została poprawnie zaimplementowana.

Może więc chodzi o coś więcej, jak mogę potwierdzić, że ProvideDefaultSorting rzeczywiście dodał sortowanie do drzewa wyrażeń.

Aktualizacja 2
Nowe pytanie odpowiedział i zaakceptowane, myślę, że powinienem zmienić tytuł dopasować pytanie bardziej. Czy powinienem zostawić obecny tytuł, ponieważ doprowadzi to ludzi z tym samym problemem do tego rozwiązania?

+0

Powinieneś zobaczyć tę odpowiedź http://stackoverflow.com/questions/36923850/how-to-know-if-orderby-was-applied-to-query – yosbel

Odpowiedz

1

Można rozwiązać ten w typie zwracanej ProvideDefaultSorting. Ten kod nie jest tworzony:

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

Ten kod jest zbudowany, ale jest podstępny, a programista dostaje to, na co zasługuje.

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

sama historia z dopiskiem (nie budować):

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

Dziękujemy za poświęcony czas, to rozwiązało jeden problem, * JEŚLI * metoda częściowa jest zaimplementowana, mogę przynajmniej upewnić się, że jest posortowana. Wtedy pozostaje tylko jeden przypadek, jak sprawdzić, czy metoda częściowa została zaimplementowana czy nie. –

+0

Nie trzeba - po prostu wykonaj sortowanie ID, a następnie podaj IOrderedQueryable do metody częściowej. –

+0

Może to być możliwe, ale chciałbym, aby zapytanie było jak najbardziej czyste. –

1

Obawiam się, że to trochę trudniejsze. Widzisz, Entity Framework będzie, w pewnych okolicznościach, silently ignore an OrderBy. Więc nie wystarczy po prostu wyszukać OrderBy w drzewie wyrażeń. The OrderBy musi znajdować się w "właściwym" miejscu, a definicja "właściwego" miejsca jest szczegółem implementacji Entity Framework.

Jak już się domyślacie, jestem w tym samym miejscu co ty; Używam wzorca repozytorium jednostek i robię Take/Skip na warstwie prezentacji. Roztwór, którego użyłem, który być może nie jest idealny, ale wystarczająco dobry dla tego, co robię, polega na tym, że nie wykonuję żadnego zamówienia do ostatniej możliwej chwili, aby upewnić się, że OrderBy jest zawsze ostatnią rzeczą w drzewie wyrażeń. Tak więc każda akcja, która ma zamiar wykonać Take/Skip (bezpośrednio lub pośrednio) wstawia najpierw OrderBy. Kod jest skonstruowany tak, że może się to zdarzyć tylko raz.

+0

w porządku , to jest bummer. Więc jeśli dobrze rozumiem, przesuwasz logikę, czy jest ona sortowana, czy nie, do warstwy prezentacji? Nie mogę powiedzieć, że to brzmi jak idealne rozwiązanie. –

+0

Ponieważ pozwalamy użytkownikom dynamicznie uciekać się do danych, posortowanie ich na warstwie prezentacji ma dla nas sens. Ale nie będę twierdził, że jest odpowiedni dla wszystkich aplikacji. Zrób to, gdy ma to dla ciebie sens, ale upewnij się, że to ostatnia rzecz, którą robisz przed Take/Skip, i to tylko raz. –

+0

Rozumiem, że zezwalasz użytkownikom na dynamiczne wprowadzanie danych (na przykład podgląd siatki). Jedna z moich przeciążonych metod listy ma parametr String sortExpression, który jest analizowany i konwertowany na wyrażenie LINQ. Może problem polega właśnie na wymuszeniu domyślnego sortowania. –

2

Stronicowanie zależy od zamówienia w mocny sposób. Dlaczego nie ściśle połączyć operacje? Oto jeden ze sposobów, aby to zrobić:

obiekty pomocnicze

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

metody przedmiotem dyskusji:

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

Jakiś czas później, wywołując metodę:

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

Mimo że jest to dobry sposób na zaoferowanie parametru sortowania, mój interfejs zawiera już przeciążenie, które zapewnia sortowanie. Zmienię moje pytanie, aby było bardziej jasne. –

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

Zmień na:

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

To nie jest idealne rozwiązanie.

Nie rozwiązuje problemu "filtr w funkcji porządkowania", o którym mówisz. To rozwiązuje "Zapomniałem zaimplementować zamawianie" lub "Nie chcę zamawiać".

testowałem tego rozwiązania w 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); 

    } 

Generuje (uwaga odwrotnej kolejności uporządkowania):

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

To rzeczywiście działa, aw tej chwili mam to jako tymczasowe rozwiązanie (aby nie przerwać kompilacji). Ale to nie jest rodzaj rozwiązania, którego szukałem. –

1

Dzięki David B Mam następujące rozwiązanie. (Musiałem dodać detekcję dla sytuacji, w której metoda częściowa nie została wykonana lub po prostu zwrócono jej parametr).

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

Zapewnia podczas kompilacji, że jeśli metoda częściowa jest zaimplementowana, powinna przynajmniej zachować wartość IOrderdQueryable.

A gdy metoda częściowa nie zostanie zaimplementowana lub po prostu zwróci jej parametr, zapytanie nie zostanie zmienione i będzie używać sortowania awaryjnego.