8
votes

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

J'écris un critère de NHibernate qui sélectionne les données prenant en charge la pagination. J'utilise le Count (*) sur () code> expression de SQL Server 2005 (+) pour obtenir le nombre total de lignes disponibles, comme suggéré par Ayende Rahien. J'ai besoin de ce nombre pour pouvoir calculer combien de pages il y a au total. La beauté de cette solution est que je n'ai pas besoin d'exécuter une deuxième requête pour obtenir la prise du nombre de lignes.

Cependant, je n'arrive pas à réussir à rédiger un critère de travail (Ayende ne fournit qu'une requête HQL). P>

Voici une requête SQL qui montre ce que je veux et cela fonctionne très bien. Notez que j'ai intentionnellement oublié la logique de pagination réelle pour vous concentrer sur le problème: P>

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


0 commentaires

6 Réponses :


0
votes

Utilisez Createmulticicriteria.

Vous pouvez exécuter 2 déclarations simples avec un seul coup à la DB de cette façon.


1 commentaires

L'utilisation de CreatemulticRITRIDERIA entraînerait deux requêtes SQL distinctes à générer. Bien qu'ils soient exécutés dans un seul lot, il ne serait toujours pas aussi efficace que l'exécution d'une seule requête. Je veux 'Sélectionner , compter () sur () en tant que rangée des articles', pas "Sélectionnez * à partir d'éléments; Sélectionnez Count (*) en tant que RowCount d'articles 'car le scénario CreatemulticRITRIDERIA me chercherait.



0
votes

Je me demande pourquoi utiliser des critères est une exigence. Vous ne pouvez pas utiliser session.createsqlquery? Si vous devez vraiment le faire dans une requête, j'aurais suggéré de retirer les objets d'article et le compte, comme: xxx

... de cette façon, vous pouvez récupérer des objets d'article de votre requête , avec le comte. Si vous rencontrez un problème avec la mise en cache d'Hibernate, vous pouvez également configurer les espaces de requête (entité / tableaux de table) associés à une requête native afin que les entrées de cache de requête, les entrées de cache de requête Stale seront effacées automatiquement.


1 commentaires

Merci de votre suggestion, mais je tiens vraiment à le faire en utilisant des critères car de cette façon, je peux facilement appliquer une pagination à bon nombre de mes critères existants en les développant simplement à l'aide d'une méthode d'extension 'Liste (Démarrer, Limit, Out TotalRowCount). En outre, en utilisant des requêtes littérales SQL (Server) rendrait ma solution moins la plate-forme agnostique, tandis que la solution actuelle qui utilise un dialecte personnalisé, serait probablement beaucoup plus facile à porter à un DBMS différent.



5
votes

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

Vous pouvez obtenir l'identifiant et charger des éléments dans une requête ultérieure: p> xxx pré>

si vous N'aimez pas cela, utilisez HQL, vous pouvez également définir le nombre maximal de résultats: P>

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


0 commentaires

0
votes

Si je comprends votre question correctement, j'ai une solution. J'ai eu un peu mal autant avec ce même problème.

Permettez-moi de décrire rapidement le problème que j'avais, pour vous assurer que nous sommes sur la même page. Mon problème est tombé à la pagination. Je souhaite afficher 10 enregistrements dans l'interface utilisateur, mais je souhaite également connaître le nombre em> total em> d'enregistrements correspondant aux critères de filtrage. Je voulais accomplir cela à l'aide de l'API de critère NH, mais lorsque vous ajoutez une projection pour le nombre de lignes, ma requête ne fonctionnait plus, et je ne me souviendrais pas de résultats (je ne me souviens pas de l'erreur spécifique, mais cela ressemble à ce que vous avez 'revenir). P>

Voici ma solution (copie et coller de mon code de production actuel). Notez que "SessionError" est le nom de l'entité commerciale que je récupère des données sur pagie pour, selon 3 critères de filtrage: ISDEV, ISRead et ISRESOLved. P>

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];


1 commentaires

Notez que je n'utilise pas une projection de rangée car cela ne compterait que les lignes réellement sélectionnées et que je ne m'intéresse pas à cela. Compter (*) sur compte toutes les lignes toutefois, atténuant la nécessité de la récupérer séparément. Néanmoins, bien qu'ils soient lots, vous exécutez deux requêtes, ce qui réussit potentiellement moins efficace que l'exécution d'un seul. Votre solution convient tout seul, mais cela ne répond pas à mon exigence d'exécution d'une seule requête SQL uniquement. OK, ce n'est pas une exigence réelle, mais je ne veux pas encore abandonner la possibilité d'optimiser ici.



0
votes

Je suggérerais d'enquêter sur le transformateur de résultats personnalisés en appelant SETRESULTTRANSFORMER () sur votre session.


0 commentaires

0
votes

Créer une propriété de formule dans la cartographie de la 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;


2 commentaires

Le total des enregistrements n'est pas une propriété de l'entité. L'ajout de cette cartographie entraînerait probablement des problèmes de mise en cache. Voir aussi: Stackoverflow.com/Questtions/1627707


Merci pour l'information! Dans mon cas, je n'avais pas le cache de requête activé et cela a été fourni via une couche de service Web apatride, par conséquent, la session n'a jamais été partagée.