ROW_NUMBER() and nhibernate - finding an item's page

前端 未结 3 876
旧巷少年郎
旧巷少年郎 2021-01-01 04:54

given a query in the form of an ICriteria object, I would like to use NHibernate (by means of a projection?) to find an element\'s order, in a manner equivalent to using

相关标签:
3条回答
  • 2021-01-01 05:34

    ICriteria has this 2 functions:

     SetFirstResult()
    

    and

     SetMaxResults()
    

    which transform your SQL statement into using ROW_NUMBER (in sql server) or limit in MySql.

    So if you want 25 records on the third page you could use:

     .SetFirstResult(2*25) 
     .SetMaxResults(25)
    
    0 讨论(0)
  • 2021-01-01 05:52

    After looking at the sources for NHibernate, I'm fairly sure that there exists no such functionality.

    I wouldn't mind, however, for someone to prove me wrong.

    In my specific setting, I did solve this problem by writing a method that takes a couple of lambdas (representing the key column, and an optional column to filter by - all properties of a specific domain entity). This method then builds the sql and calls session.CreateSQLQuery(...).UniqueResult(); I'm not claiming that this is a general purpose solution.

    To avoid the use of magic strings, I borrowed a copy of PropertyHelper<T> from this answer.

    Here's the 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; }
    }
    

    And you use it like so:

    ...
    
    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);
    

    As I said, this works for me. I'll definitely tweak it as I move forward. YMMV.

    Now, if the OP has solved this himself, then I would love to see his solution! :-)

    0 讨论(0)
  • 2021-01-01 05:53

    After trying to find an NHibernate based solution for this myself, I ultimately just added a column to the view I happened to be using:

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

    This doesn't really help if you need complex sorting options, but it does work for simple cases.

    A Criteria query, of course, would look something like this:

        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>();
        }
    
    0 讨论(0)
提交回复
热议问题