2009-10-29 13 views
8

Piszę kryteria NHibernate, które wybiera dane obsługujące stronicowanie. Używam wyrażenie COUNT(*) OVER() z SQL Server 2005 (+), aby uzyskać całkowitą liczbę dostępnych wierszy, jako suggested przez Ayende Rahien. Potrzebuję tej liczby, aby móc obliczyć, ile stron jest w sumie. Piękno tego rozwiązania polega na tym, że nie muszę wykonywać drugiego zapytania, aby uzyskać liczbę wierszy.Dodawanie projekcji do kryteriów NHibernate powoduje, że nie wykonuje on domyślnego wyboru jednostek.

Jednak nie wydaje mi się, aby udało się napisać działające kryteria (Ayende zapewnia tylko kwerendę HQL).

Oto zapytanie SQL, które pokazuje, co chcę i działa dobrze. Zauważ, że celowo pominięte rzeczywistą logiki stronicowania skupić się na problemie:

SELECT Items.*, COUNT(*) OVER() AS rowcount 
FROM Items 

oto HQL:

select 
    item, rowcount() 
from 
    Item item 

pamiętać, że funkcja rowcount() jest zarejestrowany w niestandardowych NHibernate dialektu i postanawia COUNT(*) OVER() w SQL.

Warunek jest taki, że zapytanie jest wyrażane za pomocą kryteriów. Niestety, nie wiem jak zrobić to dobrze:

var query = Session 
    .CreateCriteria<Item>("item") 
    .SetProjection(
     Projections.SqlFunction("rowcount", NHibernateUtil.Int32)); 

Ilekroć dodać projekcję, NHibernate nie wybiera item (jak to będzie bez projekcji), tylko rowcount() a ja naprawdę potrzebuję obu. Ponadto, nie mogę zaprojektować całości jako całości, tylko jej właściwości i naprawdę nie chcę ich wszystkich wymienić.

Mam nadzieję, że ktoś ma rozwiązanie tego problemu. Dzięki i tak.

Odpowiedz

5

Myślę, że nie jest to możliwe w kryteriach, ma pewne ograniczenia.

Można dostać elementy ID oraz obciążenia w kolejnym zapytaniu:

var query = Session 
    .CreateCriteria<Item>("item") 
    .SetProjection(Projections.ProjectionList() 
     .Add(Projections.SqlFunction("rowcount", NHibernateUtil.Int32)) 
     .Add(Projections.Id())); 

Jeśli nie podoba, użyć HQL, można ustawić maksymalną liczbę wyników tam:

IList<Item> result = Session 
    .CreateQuery("select item, rowcount() from item where ...") 
    .SetMaxResult(100) 
    .List<Item>(); 
0

Użyj CreateMultiCriteria.

Możesz wykonać 2 proste instrukcje z jednym trafieniem do DB w ten sposób.

+0

Korzystanie CreateMultiCriteria skutkowałoby dwóch oddzielnych zapytań SQL, które będą generowane. Chociaż będą one wykonywane w jednej partii, nadal nie będą tak wydajne, jak wykonywanie tylko jednego zapytania. Chcę "SELECT *, COUNT (*) OVER() AS rowcount FROM Items", a nie "SELECT * FROM Items; WYBIERZ LICZBĘ (*) JAKO liczba wierszy z elementów, jak przyszedłby scenariusz CreateMultiCriteria. –

0

Zastanawiam się, dlaczego stosowanie Kryteriów jest wymogiem. Nie możesz użyć session.CreateSQLQuery? Jeśli naprawdę musi to zrobić w jednym zapytaniu, bym sugerował odciągnięcie obiekty pozycji i zliczania, jak:

select {item.*}, count(*) over() 
from Item {item} 

... w ten sposób można wrócić obiektów element z zapytaniem, wraz z liczyć. Jeśli wystąpi problem z buforowaniem Hibernate, można również skonfigurować przestrzenie zapytania (bufory encji/tabeli) powiązane z rodzimym zapytaniem, aby wpisy w pamięci podręcznej starych zapytań były automatycznie usuwane.

+0

Dzięki za sugestię, ale naprawdę chcę to zrobić za pomocą kryteriów, ponieważ w ten sposób mogę łatwo zastosować stronicowanie do wielu moich istniejących kryteriów, po prostu rozszerzając je przy użyciu metody rozszerzenia "List (start, limit, out totalRowCount)". Poza tym, używanie dosłownych zapytań SQL (Server) sprawiłoby, że moje rozwiązanie byłoby mniej agnostyczne dla platformy, podczas gdy obecne rozwiązanie, które używa niestandardowego dialektu, byłoby prawdopodobnie łatwiejsze do przeniesienia do innego systemu DBMS. –

0

Jeśli dobrze rozumiem twoje pytanie, mam rozwiązanie. Z tym samym problemem zmagałem się trochę.

Pozwól mi szybko opisać problem, który miałem, aby upewnić się, że jesteśmy na tej samej stronie. Mój problem sprowadził się do stronicowania. Chcę wyświetlić 10 rekordów w interfejsie użytkownika, ale chcę też poznać liczbę rekordów o łącznej liczbie, które pasują do kryteriów filtru. Chciałem to osiągnąć za pomocą interfejsu API NH, ale dodając rzut dla liczby wierszy, moje zapytanie przestało działać i nie uzyskałbym żadnych wyników (nie pamiętam konkretnego błędu, ale brzmi to jak dostaję).

Oto moje rozwiązanie (skopiuj & wklej z mojego obecnego kodu produkcyjnego). Zauważ, że "SessionError" to nazwa jednostki biznesowej, dla której pobieram dane stronicowane, zgodnie z 3 kryterium filtru: IsDev, IsRead i IsResolved.

ICriteria crit = CurrentSession.CreateCriteria(typeof (SessionError)) 
    .Add(Restrictions.Eq("WebApp", this)); 

if (isDev.HasValue) 
    crit.Add(Restrictions.Eq("IsDev", isDev.Value)); 

if (isRead.HasValue) 
    crit.Add(Restrictions.Eq("IsRead", isRead.Value)); 

if (isResolved.HasValue) 
    crit.Add(Restrictions.Eq("IsResolved", isResolved.Value)); 

// Order by most recent 
crit.AddOrder(Order.Desc("DateCreated")); 

// Copy the ICriteria query to get a row count as well 
ICriteria critCount = CriteriaTransformer.Clone(crit) 
    .SetProjection(Projections.RowCountInt64()); 
critCount.Orders.Clear(); 

// NOW add the paging vars to the original query 
crit = crit 
    .SetMaxResults(pageSize) 
    .SetFirstResult(pageNum_oneBased * pageSize); 

// Set up a multi criteria to get your data in a single trip to the database 
IMultiCriteria multCrit = CurrentSession.CreateMultiCriteria() 
    .Add(crit) 
    .Add(critCount); 

// Get the results 
IList results = multCrit.List(); 

List<SessionError> sessionErrors = new List<SessionError>(); 
foreach (SessionError sessErr in ((IList)results[0])) 
    sessionErrors.Add(sessErr); 

numResults = (long)((IList)results[1])[0]; 

Tworzę więc moje podstawowe kryteria z opcjonalnymi ograniczeniami. Następnie KLIKAMY i dodam rzut wierszy do kryteriów KLONOWANIA. Zauważ, że sklonowałem to przed dodaniem ograniczeń stronicowania. Następnie skonfigurowałem IMultiCriteria, aby zawierała oryginalne i sklonowane obiekty ICriteria, i użyłem IMultiCriteria do ich wykonania. Teraz mam dane stronicowane z oryginalnych ICriteria (i tylko przeciągnąłem potrzebne dane przez cały przewód), a także surową liczbę rzeczywistych rekordów pasujących do moich kryteriów (przydatne do wyświetlania lub tworzenia linków przywoławczych, lub cokolwiek innego). Ta strategia działa dobrze dla mnie. Mam nadzieję, że to będzie pomocne.

+0

Należy zauważyć, że nie używam rzutu rzutu RowCount, ponieważ to zliczałoby tylko faktycznie wybrane wiersze i nie jestem tym zainteresowany. COUNT (*) OVER zlicza wszystkie wiersze, zmniejszając potrzebę oddzielnego pobierania. Mimo to, mimo że są one grupowane, wykonuje się dwa zapytania, które są potencjalnie mniej skuteczne niż wykonywanie tylko jednego. Twoje rozwiązanie jest w porządku samo w sobie, ale nie spełnia mojego wymogu wykonania tylko jednego zapytania SQL. OK, to nie jest prawdziwy wymóg, ale nie chcę zrezygnować z możliwości optymalizacji. –

0

Proponuję zbadać niestandardowy transformator wyników, wywołując SetResultTransformer() w sesji.

0

Tworzenie właściwości formuły w mapowaniu klasy:

<property name="TotalRecords" formula="count(*) over()" type="Int32" not-null="true"/>; 

IList<...> result = criteria.SetFirstResult(skip).SetMaxResults(take).List<...>(); 
totalRecords = (result != null && result.Count > 0) ? result[0].TotalRecords : 0; 
return result; 
+0

Łączna liczba rekordów nie jest własnością jednostki. Dodanie tego odwzorowania prawdopodobnie spowoduje problemy z buforowaniem. Zobacz także: http://stackoverflow.com/questions/1627707 – iammichael

+0

Dzięki za heads up! W moim przypadku nie włączono pamięci podręcznej zapytań, która została dostarczona przez bezpaństwową warstwę usługi sieci Web, dlatego sesja nigdy nie była udostępniana. –

Powiązane problemy