2009-10-29 11 views
8

Ich schreibe ein NHibernate-Kriterium, das Daten auswählt, die Paging unterstützen. Ich verwende den Ausdruck COUNT(*) OVER() von SQL Server 2005 (+), um die Gesamtzahl der verfügbaren Zeilen zu erhalten, wie suggested von Ayende Rahien. Ich brauche diese Zahl, um berechnen zu können, wie viele Seiten es insgesamt gibt. Das Schöne an dieser Lösung ist, dass ich keine zweite Abfrage ausführen muss, um die Zeilenanzahl zu erhalten.Das Hinzufügen einer Projektion zu einem NHibernate-Kriterium verhindert die Ausführung der standardmäßigen Entitätsauswahl

Allerdings kann ich nicht scheinen, um ein Arbeitskriterium zu schreiben (Ayende bietet nur eine HQL-Abfrage).

Hier ist eine SQL-Abfrage, die zeigt, was ich will und es funktioniert gut. Beachten Sie, dass ich absichtlich weggelassen, die tatsächliche Paging-Logik auf das Problem zu konzentrieren:

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

Hier ist die HQL:

select 
    item, rowcount() 
from 
    Item item 

Beachten Sie, dass die rowcount() Funktion in einem benutzerdefinierten NHibernate Dialekt registriert ist und beschließt, COUNT(*) OVER() in SQL.

Eine Anforderung ist, dass die Abfrage mithilfe eines Kriteriums ausgedrückt wird. Leider weiß ich nicht, wie es richtig zu machen:

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

Jedes Mal, wenn ich einen Vorsprung hinzufügen, NHibernate nicht item (wie es ohne Projektion würde) nicht auswählen, sondern nur die rowcount() während ich wirklich brauche beides. Auch kann ich nicht item als Ganzes projizieren, nur seine Eigenschaften und ich möchte wirklich nicht alle von ihnen auflisten.

Ich hoffe, jemand hat eine Lösung für dieses Problem. Danke trotzdem.

Antwort

5

Ich denke, dass es in Kriterien nicht möglich ist, hat es einige Grenzen.

Sie könnten die ID und Teile der Ladung in einer nachfolgenden Abfrage erhalten:

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

Wenn Sie es nicht mögen, verwenden HQL, können Sie die maximale Anzahl der Ergebnisse dort setzen:

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

Verwenden Sie CreateMultiCriteria.

Sie können 2 einfache Anweisungen mit nur einem Treffer in der DB auf diese Weise ausführen.

+0

Mit CreateMultiCriteria in zwei separaten SQL-Abfragen führen würde generiert werden. Obwohl sie in einem einzigen Stapel ausgeführt werden, wäre sie dennoch nicht so effizient wie die Ausführung einer einzelnen Abfrage. Ich möchte 'SELECT *, COUNT (*) OVER() als rowcount FROM Items', nicht 'SELECT * FROM Items; SELECT COUNT (*) AS rowcount FROM Items 'wie das CreateMultiCriteria-Szenario würde mich bekommen. –

0

Ich frage mich, warum die Verwendung von Kriterien eine Voraussetzung ist. Können Sie session.CreateSQLQuery nicht verwenden? Wenn Sie es wirklich in einer Abfrage tun müssen, hätte ich vorgeschlagen, die Item-Objekte zurückziehen und der Graf, wie:

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

... diese Weise können Sie zurück Artikel Objekte aus der Abfrage zu bekommen, zusammen mit der Anzahl. Wenn bei der Zwischenspeicherung von Hibernate ein Problem auftritt, können Sie auch die Abfragebereiche (Entitäts-/Tabellencaches) konfigurieren, die einer systemeigenen Abfrage zugeordnet sind, sodass Einträge für veraltete Abfragecaches automatisch gelöscht werden.

+0

Vielen Dank für Ihren Vorschlag, aber ich möchte es wirklich mit Criteria machen, weil ich auf diese Weise einfach Paging auf viele meiner bestehenden Kriterien anwenden kann, indem ich sie einfach mit der Erweiterungsmethode 'List (start, limit, out totalRowCount)' erweitere. Außerdem würde die Verwendung von literalen SQL (Server) -Abfragen meine Lösung weniger plattformunabhängig machen, während die aktuelle Lösung, die einen angepassten Dialekt verwendet, wahrscheinlich viel einfacher auf ein anderes DBMS portiert werden könnte. –

0

Wenn ich Ihre Frage richtig verstehe, habe ich eine Lösung. Ich habe mit dem gleichen Problem ziemlich gerungen.

Lassen Sie mich schnell das Problem beschreiben, das ich hatte, um sicherzustellen, dass wir auf der gleichen Seite sind. Mein Problem kam zum Blättern. Ich möchte 10 Datensätze in der Benutzeroberfläche anzeigen, aber ich möchte auch die Gesamt Anzahl der Datensätze kennen, die den Filterkriterien entsprechen. Ich wollte dies mit der NH-Kriterien-API erreichen, aber beim Hinzufügen einer Projektion für die Zeilenanzahl funktionierte meine Abfrage nicht mehr, und ich würde keine Ergebnisse erhalten (ich erinnere mich nicht an den spezifischen Fehler, aber es klingt nach dem, was Sie tun werde wieder).

Hier ist meine Lösung (kopieren Sie & Paste aus meinem aktuellen Produktionscode). Beachten Sie, dass "SessionError" der Name der Geschäftsentität ist, für die ich ausgelagerte Daten nach 3 Filterkriterien abrufe: IsDev, IsRead und 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]; 

Also ich meine Basiskriterien mit optionalen Einschränkungen erstellen. Dann klicke ich es und füge eine Zeilenanzahl-Projektion zu den CLONED-Kriterien hinzu. Beachten Sie, dass ich es klonen vor ich die Paging-Einschränkungen hinzufügen. Dann richte ich einen IMultiCriteria ein, um die ursprünglichen und geklonten ICriteria-Objekte zu enthalten, und verwende den IMultiCriteria, um beide auszuführen. Jetzt habe ich meine Daten aus dem ursprünglichen ICriteria ausgelagert (und ich habe nur die Daten gezogen, die ich über den Draht brauche), und auch eine rohe Zählung, wie viele tatsächliche Datensätze meinen Kriterien entsprachen (nützlich zum Anzeigen oder Erstellen von Paging-Links oder was auch immer). Diese Strategie hat für mich gut funktioniert. Ich hoffe, das ist hilfreich.

+0

Beachten Sie, dass ich keine RowCount-Projektion verwende, da dies nur die tatsächlich ausgewählten Zeilen zählt, und das interessiert mich nicht. COUNT (*) OVER zählt alle Zeilen, verringert jedoch die Notwendigkeit, diese separat abzurufen. Obwohl sie stapelweise vorliegen, führen Sie zwei Abfragen aus, was natürlich potentiell weniger effizient ist als die Ausführung von nur einer Abfrage. Ihre Lösung ist für sich allein in Ordnung, erfüllt aber nicht meine Anforderung, nur eine einzige SQL-Abfrage auszuführen. OK, das ist keine echte Anforderung, aber ich möchte noch nicht auf die Möglichkeit verzichten, hier zu optimieren. –

0

Ich würde vorschlagen, benutzerdefinierte Ergebnis Transformator durch Aufrufen von SetResultTransformer() auf Ihrer Sitzung untersuchen.

0

eine Formel-Eigenschaft in der Klasse Mapping erstellen:

<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

Die Gesamtzahl der Datensätze ist keine Eigenschaft der Entität. Das Hinzufügen dieser Zuordnung würde wahrscheinlich zu Caching-Problemen führen. Siehe auch: http://StackOverflow.com/Questions/1627707 – iammichael

+0

Danke für die Köpfe hoch! In meinem Fall hatte ich den Abfragecache nicht aktiviert und dies wurde über eine statusfreie Webdienstschicht bereitgestellt, daher wurde die Sitzung nie freigegeben. –