L'ajout d'une projection à un critère NHibernate l'empêche d'effectuer la sélection d'entité par défaut

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

Question

J'écris un critère NHibernate qui sélectionne les données prenant en charge la pagination. J'utilise l'expression COUNT (*) OVER () de SQL Server 2005 (+) pour obtenir le nombre total de lignes disponibles, sous la forme suggéré par Ayende Rahien. J'ai besoin de ce nombre pour pouvoir calculer le nombre total de pages. L'avantage de cette solution est que je n'ai pas besoin d'exécuter une deuxième requête pour obtenir le nombre de lignes.

Cependant, je n'arrive pas à écrire un critère de travail (Ayende fournit uniquement une requête HQL).

Voici une requête SQL qui montre ce que je veux et qui fonctionne très bien. Notez que j'ai volontairement laissé de côté la logique de pagination proprement dite pour me concentrer sur le problème:

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

Voici le HQL:

select
    item, rowcount()
from 
    Item item

Notez que la fonction rowcount () est enregistrée dans un dialecte NHibernate personnalisé et résout en COUNT (*) OVER () en SQL.

Une exigence est que la requête soit exprimée à l'aide d'un critère. Malheureusement, je ne sais pas comment bien faire les choses:

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

Chaque fois que j'ajoute une projection, NHibernate ne sélectionne pas item (comme ce serait le cas sans projection), mais simplement le rowcount () <)> alors que j'ai vraiment besoin des deux. De plus, je n'arrive pas à projeter élément dans son ensemble, il ne s'agit que de propriétés et je ne souhaite vraiment pas toutes les répertorier.

J'espère que quelqu'un a une solution à cela. Merci quand même.

Était-ce utile?

La solution

Je pense que ce n'est pas possible dans les critères, il a certaines limites.

Vous pouvez obtenir l'id et charger des éléments dans une requête ultérieure:

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

Si vous ne l'aimez pas, utilisez HQL, vous pouvez également y définir le nombre maximal de résultats:

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

Autres conseils

Utilisez CreateMultiCriteria.

Vous pouvez ainsi exécuter 2 instructions simples en une seule fois sur la base de données.

Je me demande pourquoi utiliser des critères est une exigence. Ne pouvez-vous pas utiliser session.CreateSQLQuery? Si vous deviez vraiment le faire en une seule requête, j'aurais suggéré de retirer les objets Item et le nombre, comme suit:

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

... De cette façon, vous pouvez récupérer les objets Item de votre requête, ainsi que le nombre. Si vous rencontrez un problème avec la mise en cache de Hibernate, vous pouvez également configurer les espaces de requête (caches d'entité / de table) associés à une requête native afin que les entrées de cache de requête obsolètes soient effacées automatiquement.

Si je comprends bien votre question, j'ai une solution. Je me suis beaucoup battu avec le même problème.

Permettez-moi de décrire rapidement le problème que j’avais, afin de nous assurer que nous sommes sur la même page. Mon problème était lié à la pagination. Je souhaite afficher 10 enregistrements dans l'interface utilisateur, mais je souhaite également connaître le nombre total d'enregistrements correspondant aux critères de filtrage. Je voulais accomplir cela en utilisant l'API de critères NH, mais lors de l'ajout d'une projection pour le nombre de lignes, ma requête ne fonctionnait plus et je n'obtenais aucun résultat (je ne me souviens pas de l'erreur spécifique, mais cela ressemble obtiens).

Voici ma solution (copier-coller de mon code de production actuel). Notez que " SessionError " est le nom de l'entité commerciale pour laquelle je récupère les données paginées, selon le critère de filtre 3: IsDev, IsRead et 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];

Je crée donc mes critères de base, avec des restrictions optionnelles. Ensuite, je le clone et j'ajoute une projection du nombre de lignes aux critères CLONED. Notez que je le clone avant d’ajouter les restrictions de pagination. Ensuite, j'ai configuré un IMultiCriteria pour qu'il contienne les objets ICriteria d'origine et clonés, et j'utilise les IMultiCriteria pour les exécuter. Maintenant, j'ai mes données paginées des ICriteria d'origine (et je n'ai fait que glisser les données dont j'ai besoin sur le réseau), ainsi qu'un compte brut du nombre d'enregistrements correspondant à mes critères (utile pour l'affichage ou la création de liens de pagination, etc.). Cette stratégie a bien fonctionné pour moi. J'espère que cela vous sera utile.

Je suggérerais d'étudier le transformateur de résultat personnalisé en appelant SetResultTransformer () dans votre session.

Créez une propriété de formule dans le mappage de classe:

<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;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top