2010-01-04 17 views
4

étant donné une requête sous la forme d'un objet ICriteria, je voudrais utiliser NHibernate (au moyen d'une projection?) Pour trouver l'ordre d'un élément, d'une manière équivalente à en utilisantROW_NUMBER() et nhibernate - trouver la page d'un article

SELECT ROW_NUMBER() OVER (...) 

pour trouver l'index d'un élément spécifique dans la requête. (J'ai besoin de cela pour une fonctionnalité "saut à la page" dans la radiomessagerie) des suggestions?

NOTE: Je ne veux pas aller à une page étant donné son numéro - je sais comment faire cela - je veux obtenir l'INDEX de l'élément afin que je puisse le diviser par taille de page et obtenir l'index de page.

+0

C'est quelque chose que je cherche aussi. Lorsque vous avez posté cette question, j'attendais avec impatience toutes les réponses ... Après avoir regardé les sources de NHibernate, je suis à peu près sûr qu'il n'existe pas de telle fonctionnalité. –

+0

En outre, la syntaxe quelque peu maladroite de la fonction row_number() rend probablement difficile l'implémentation de cette fonctionnalité. –

+0

Considéreriez-vous une solution qui ne soit pas basée sur ICriteria et spécifique à SQL Server? (La solution ne nécessite pas d'entrée de chaînes magiques, est un peu limitée en fonctionnalités mais profite de vos entités de domaine ...) –

Répondre

3

ICriteria a ce 2 fonctions:

SetFirstResult() 

et

SetMaxResults() 

qui transforment votre instruction SQL en utilisant ROW_NUMBER (dans le serveur SQL) ou la limite MySql.

Donc, si vous voulez 25 dossiers sur la troisième page, vous pouvez utiliser:

.SetFirstResult(2*25) 
.SetMaxResults(25) 
+1

merci, mais ce que je veux faire est un peu différent - j'ai un objet, et je veux trouver son index, afin que je puisse trouver la page de l'objet. alors je peux utiliser ces méthodes suggérées pour aller chercher cette page. –

5

Après avoir examiné les sources pour NHibernate, je suis assez sûr qu'il n'y existe pas une telle fonctionnalité.

Cela ne me dérangerait pas, cependant, que quelqu'un me prouve le contraire.

Dans mon propre mise en, j'ai résolu ce problème en écrivant une méthode qui prend quelques lambdas (représentant la colonne de clé, et une colonne en option pour filtrer - toutes les propriétés d'une entité de domaine spécifique). Cette méthode construit ensuite le sql et appelle session.CreateSQLQuery(...).UniqueResult(); Je ne prétends pas que c'est une solution polyvalente. Pour éviter l'utilisation de chaînes magiques, j'ai emprunté une copie de PropertyHelper<T> de this answer.

Voici le code:

public abstract class RepositoryBase<T> where T : DomainEntityBase 
{ 
    public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector, TWhere whereValue) where TWhere : DomainEntityBase 
    { 
     if (entity == null || entity.Id == Guid.Empty) 
     { 
      return -1; 
     } 

     var entityType = typeof(T).Name; 

     var keyField = PropertyHelper<T>.GetProperty(uniqueSelector).Name; 
     var keyValue = uniqueSelector.Compile()(entity); 

     var innerWhere = string.Empty; 

     if (whereSelector != null) 
     { 
      // Builds a column name that adheres to our naming conventions! 
      var filterField = PropertyHelper<T>.GetProperty(whereSelector).Name + "Id"; 

      if (whereValue == null) 
      { 
       innerWhere = string.Format(" where [{0}] is null", filterField); 
      } 
      else 
      { 
       innerWhere = string.Format(" where [{0}] = :filterValue", filterField); 
      } 
     } 

     var innerQuery = string.Format("(select [{0}], row_number() over (order by {0}) as RowNum from [{1}]{2}) X", keyField, entityType, innerWhere); 

     var outerQuery = string.Format("select RowNum from {0} where {1} = :keyValue", innerQuery, keyField); 

     var query = _session 
      .CreateSQLQuery(outerQuery) 
      .SetParameter("keyValue", keyValue); 

     if (whereValue != null) 
     { 
      query = query.SetParameter("filterValue", whereValue.Id); 
     } 

     var sqlRowNumber = query.UniqueResult<long>(); 

     // The row_number() function is one-based. Our index should be zero-based. 
     sqlRowNumber -= 1; 

     return sqlRowNumber; 
    } 

    public long GetIndexOf<TUnique>(T entity, Expression<Func<T, TUnique>> uniqueSelector) 
    { 
     return GetIndexOf(entity, uniqueSelector, null, (DomainEntityBase)null); 
    } 

    public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector) where TWhere : DomainEntityBase 
    { 
     return GetIndexOf(entity, uniqueSelector, whereSelector, whereSelector.Compile()(entity)); 
    } 
} 

public abstract class DomainEntityBase 
{ 
    public virtual Guid Id { get; protected set; } 
} 

Et vous l'utiliser comme ceci:

... 

public class Book : DomainEntityBase 
{ 
    public virtual string Title { get; set; } 
    public virtual Category Category { get; set; } 
    ... 
} 

public class Category : DomainEntityBase { ... } 

public class BookRepository : RepositoryBase<Book> { ... } 

... 

var repository = new BookRepository(); 
var book = ... a persisted book ... 

// Get the index of the book, sorted by title. 
var index = repository.GetIndexOf(book, b => b.Title); 

// Get the index of the book, sorted by title and filtered by that book's category. 
var indexInCategory = repository.GetIndexOf(book, b => b.Title, b => b.Category); 

Comme je l'ai dit, cela fonctionne pour moi. Je vais certainement le modifier au fur et à mesure que j'avance. YMMV.

Maintenant, si l'OP a résolu cela lui-même, alors j'aimerais voir sa solution! :-)

+0

super, je pourrais essayer si mon plan de projection échoue :) –

0

Après avoir essayé de trouver une solution à base de NHibernate pour moi-même, je viens d'ajouter finalement une colonne à la vue je me trouvais à l'aide:

CREATE VIEW vw_paged AS 
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS [Row], p.column1, p.column2 
FROM paged_table p 

Cela ne contribue pas vraiment si vous avez besoin options de tri complexes, mais cela fonctionne pour les cas simples.

Une requête de critères, bien sûr, ressemblerait à quelque chose comme ceci:

public static IList<Paged> GetRange(string search, int rows) 
    { 
     var match = DbSession.Current.CreateCriteria<Job>() 
      .Add(Restrictions.Like("Id", search + '%')) 
      .AddOrder(Order.Asc("Id")) 
      .SetMaxResults(1) 
      .UniqueResult<Paged>(); 

     if (match == null) 
      return new List<Paged>(); 
     if (rows == 1) 
      return new List<Paged> {match}; 

     return DbSession.Current.CreateCriteria<Paged>() 
      .Add(Restrictions.Like("Id", search + '%')) 
      .Add(Restrictions.Ge("Row", match.Row)) 
      .AddOrder(Order.Asc("Id")) 
      .SetMaxResults(rows) 
      .List<Paged>(); 
    }