L'aggiunta di una proiezione a un criterio NHibernate ne impedisce l'esecuzione della selezione di entità predefinita

StackOverflow https://stackoverflow.com/questions/1644776

Domanda

Sto scrivendo un criterio NHibernate che seleziona i dati a supporto del paging. Sto usando l'espressione COUNT (*) OVER () di SQL Server 2005 (+) per ottenere il numero totale di righe disponibili, come suggerito da Ayende Rahien. Ho bisogno di quel numero per poter calcolare quante pagine ci sono in totale. Il bello di questa soluzione è che non ho bisogno di eseguire una seconda query per ottenere il conteggio delle righe.

Tuttavia, non riesco a riuscire a scrivere un criterio funzionante (Ayende fornisce solo una query HQL).

Ecco una query SQL che mostra ciò che voglio e funziona perfettamente. Si noti che ho intenzionalmente escluso la logica di paging effettiva per concentrarmi sul problema:

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

Ecco l'HQL:

select
    item, rowcount()
from 
    Item item

Nota che la funzione rowcount () è registrata in un dialetto NHibernate personalizzato e si risolve in COUNT (*) OVER () in SQL.

Un requisito è che la query sia espressa usando un criterio. Sfortunatamente, non so come farlo bene:

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

Ogni volta che aggiungo una proiezione, NHibernate non seleziona item (come farebbe senza una proiezione), solo il rowcount () mentre ho davvero bisogno di entrambi. Inoltre, non riesco a proiettare item nel suo insieme, solo le sue proprietà e non voglio davvero elencarle tutte.

Spero che qualcuno abbia una soluzione a questo. Grazie comunque.

È stato utile?

Soluzione

Penso che non sia possibile in Criteri, ha dei limiti.

È possibile ottenere l'id e caricare gli elementi in una query successiva:

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

Se non ti piace, usa HQL, puoi impostare anche qui il numero massimo di risultati:

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

Altri suggerimenti

Usa CreateMultiCriteria.

In questo modo puoi eseguire 2 semplici istruzioni con un solo hit sul DB.

Mi chiedo perché usare Criteria sia un requisito. Non puoi usare session.CreateSQLQuery? Se davvero dovessi farlo in una sola query, avrei suggerito di ritirare gli oggetti Item e il conteggio, come:

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

... in questo modo puoi recuperare gli oggetti Item dalla tua query, insieme al conteggio. Se si riscontra un problema con la memorizzazione nella cache di Hibernate, è anche possibile configurare gli spazi di query (cache entità / tabella) associati a una query nativa in modo che le voci della cache delle query non aggiornate vengano cancellate automaticamente.

Se capisco bene la tua domanda, ho una soluzione. Ho faticato parecchio con questo stesso problema.

Lasciami descrivere rapidamente il problema che ho avuto, per assicurarmi di essere sulla stessa pagina. Il mio problema è arrivato al paging. Voglio visualizzare 10 record nell'interfaccia utente, ma voglio anche conoscere il numero totale di record che soddisfano i criteri di filtro. Volevo ottenere questo risultato utilizzando l'API dei criteri NH, ma quando aggiungevo una proiezione per il conteggio delle righe, la mia query non funzionava più e non avrei ottenuto alcun risultato (non ricordo l'errore specifico, ma suona come quello che tu stai arrivando).

Ecco la mia soluzione (copia e incolla dal mio attuale codice di produzione). Tieni presente che " SessionError " è il nome dell'entità commerciale per cui sto recuperando i dati paginati, in base a 3 criteri di filtro: IsDev, IsRead e 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];

Quindi creo i miei criteri di base, con restrizioni opzionali. Quindi lo CLONE e aggiungo una proiezione di conteggio righe ai criteri CLONED. Nota che l'ho clonato prima aggiungo le restrizioni di paging. Quindi ho impostato un IMultiCriteria per contenere gli oggetti ICriteria originali e clonati e uso IMultiCriteria per eseguirli entrambi. Ora ho i miei dati di paging dall'ICriteria originale (e ho trascinato solo i dati di cui ho bisogno attraverso il filo), e anche un conteggio grezzo di quanti record effettivi corrispondevano ai miei criteri (utile per visualizzare o creare collegamenti di paging o altro). Questa strategia ha funzionato bene per me. Spero sia utile.

Suggerirei di esaminare il trasformatore di risultati personalizzato chiamando SetResultTransformer () sulla tua sessione.

Crea una proprietà formula nel mapping delle classi:

<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;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top