8 Stimmen

Das Hinzufügen einer Projektion zu einem NHibernate-Kriterium verhindert, dass es eine Standard-Entitätsauswahl durchführt

Ich schreibe eine NHibernate Kriterien, die Daten unterstützt Paging auswählt. Ich verwende die COUNT(*) OVER() Ausdruck von SQL Server 2005(+), um die Gesamtzahl der verfügbaren Zeilen zu ermitteln, da vorgeschlagen von Ayende Rahien. Ich brauche diese Zahl, um zu berechnen, wie viele Seiten es insgesamt gibt. Das Schöne an dieser Lösung ist, dass ich keine zweite Abfrage ausführen muss, um die Zeilenzahl zu erhalten.

Ich scheine es jedoch nicht zu schaffen, ein funktionierendes Kriterium zu schreiben (Ayende bietet nur eine HQL-Abfrage).

Hier ist eine SQL-Abfrage, die zeigt, was ich will, und sie funktioniert einwandfrei. Beachten Sie, dass ich absichtlich die eigentliche Paging-Logik weggelassen habe, um mich auf das Problem zu konzentrieren:

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

Hier ist das HQL:

select
    item, rowcount()
from 
    Item item

Beachten Sie, dass die rowcount() Funktion ist in einem benutzerdefinierten NHibernate-Dialekt registriert und wird aufgelöst in COUNT(*) OVER() in SQL.

Eine Voraussetzung ist, dass die Abfrage durch ein Kriterium ausgedrückt wird. Leider weiß ich nicht, wie ich das richtig hinbekomme:

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

Wenn ich eine Projektion hinzufüge, wählt NHibernate nicht item (wie ohne eine Projektion), sondern nur die rowcount() während ich beides wirklich brauche. Außerdem kann ich scheinbar nicht projizieren item als Ganzes, sondern nur seine Eigenschaften, und die möchte ich wirklich nicht alle aufzählen.

Ich hoffe, jemand hat eine Lösung für dieses Problem. Trotzdem vielen Dank.

5voto

Stefan Steinegger Punkte 62197

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

Sie können die ID abrufen und die Elemente in einer nachfolgenden Abfrage laden:

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

Wenn Ihnen das nicht gefällt, verwenden Sie HQL, auch dort können Sie die maximale Anzahl der Ergebnisse festlegen:

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

0voto

dmonlord Punkte 1350

Verwenden Sie CreateMultiCriteria.

Auf diese Weise können Sie 2 einfache Anweisungen mit nur einem Zugriff auf die DB ausführen.

0voto

RMorrisey Punkte 7499

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

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

...auf diese Weise erhalten Sie Item-Objekte aus Ihrer Abfrage zurück, zusammen mit der Anzahl. Wenn Sie ein Problem mit der Zwischenspeicherung von Hibernate haben, können Sie die Abfragebereiche (Entity-/Tabellen-Caches), die mit einer nativen Abfrage verbunden sind, auch so konfigurieren, dass veraltete Abfrage-Cache-Einträge automatisch gelöscht werden.

0voto

Samuel Meacham Punkte 10025

Wenn ich Ihre Frage richtig verstehe, habe ich eine Lösung. Ich habe mit dem gleichen Problem ziemlich viel zu kämpfen gehabt.

Lassen Sie mich kurz das Problem schildern, das ich hatte, um sicherzustellen, dass wir auf derselben Seite stehen. Mein Problem war das Paging. Ich möchte 10 Datensätze in der Benutzeroberfläche anzeigen, aber ich möchte auch wissen, welche insgesamt Anzahl der Datensätze, die den Filterkriterien entsprechen. Ich wollte dies mit der NH-Kriterien-API erreichen, aber als ich eine Projektion für die Zeilenzahl hinzufügte, funktionierte meine Abfrage nicht mehr, und ich erhielt keine Ergebnisse (ich erinnere mich nicht an den spezifischen Fehler, aber es klingt wie das, was Sie bekommen).

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

Ich erstelle also meine Basiskriterien mit optionalen Einschränkungen. Dann klone ich es und füge eine Zeilenzählprojektion zu den geklonten Kriterien hinzu. Beachten Sie, dass ich es klone antes de Ich füge die Auslagerungsbeschränkungen hinzu. Dann richte ich ein IMultiCriteria ein, das die ursprünglichen und geklonten ICriteria-Objekte enthält, und verwende das IMultiCriteria, um beide auszuführen. Jetzt habe ich meine ausgelagerten Daten aus dem ursprünglichen ICriteria (und ich habe nur die Daten, die ich brauche, über die Leitung gezogen) und auch eine grobe Zählung, wie viele tatsächliche Datensätze meinen Kriterien entsprechen (nützlich für die Anzeige oder das Erstellen von Auslagerungslinks oder was auch immer). Diese Strategie hat sich für mich bewährt. Ich hoffe, dies ist hilfreich.

0voto

Rashack Punkte 4638

Ich würde vorschlagen, benutzerdefinierte Ergebnis-Transformator durch Aufruf SetResultTransformer() auf Ihrer Sitzung zu untersuchen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X