【C#】NHibernate下实现SQL2000分页(SQL篇)
拜读《NHibernate 2.0.1 下实现SQL2000真分页》http://www.cnblogs.com/unfeelin/archive/2009/03/15/MsSql2000Dialect.html,觉得作者分页的条件比较苛刻,且使用临时表方法,多线程容易出现问题。
根据我的《SQL Server 2000的分页方法(SQL篇)》http://www.cnblogs.com/litou/articles/1678043.html,使用方法3扩展NHibernate下SQL2000的分页方法,希望抛砖引肉举一反三。使用其他方法类似处理。
本人使用NHibernate 2.1.0 beta
using System; using System.Data; using System.Collections; using System.Collections.Generic; using System.Text; using NHibernate.Dialect; using NHibernate.SqlCommand; using NHibernate.Util; namespace Extends.NHibernate { /// <summary> /// 对NHibernate的SqlServer2000分页扩展 /// </summary> public class PagingMsSql2000Dialect : MsSql2000Dialect { /// <summary> /// 启用分页支持 /// </summary> public override bool SupportsLimitOffset { get { return true; } } /// <summary> /// 返回带分页功能语句 /// </summary> /// <param name="querySqlString"></param> /// <param name="offset"></param> /// <param name="limit"></param> /// <returns></returns> public override SqlString GetLimitString(SqlString querySqlString, int offset, int limit) { if (offset == 0) { return base.GetLimitString(querySqlString, offset, limit); } //检查order by语句 int orderIndex = querySqlString.LastIndexOfCaseInsensitive(" order by "); if (orderIndex == -1) { return base.GetLimitString(querySqlString, offset, limit); } //获取select语句部分的字段名和别名 int fromIndex = GetFromIndex(querySqlString); SqlString select = querySqlString.Substring(0, fromIndex); List<SqlString> columnsOrAliases; //别名列表 Dictionary<SqlString, SqlString> columnToAlias; //字段名对应别名词典,键为字段,值为别名 ExtractColumnOrAliasNames(select, out columnsOrAliases, out columnToAlias); //获取order by表达式 SqlString orderBy = querySqlString.Substring(orderIndex).Trim(); SqlString[] sortExpressions = orderBy.Substring(9).Split(","); int pageSize = limit - offset; int selectInsertPoint = GetAfterSelectInsertPoint(querySqlString); SqlStringBuilder pagingBuilder = new SqlStringBuilder(); pagingBuilder.Add("select * from (select top " + pageSize + " * from ("); pagingBuilder.Add(querySqlString.Insert(selectInsertPoint, " top " + limit)); pagingBuilder.Add(") as __page_second_filter order by " + StringHelper.Join(",", FitSortExpressions(sortExpressions, columnToAlias, true))); pagingBuilder.Add(") as __page_first_filter order by " + StringHelper.Join(",", FitSortExpressions(sortExpressions, columnToAlias, false))); return pagingBuilder.ToSqlString(); } /// <summary> /// 转换排序字段为别名,且按需调转排序方向 /// </summary> /// <param name="sortExpressions"></param> /// <param name="columnToAlias"></param> /// <param name="switchOrderDirection"></param> /// <returns></returns> private static SqlString[] FitSortExpressions(SqlString[] sortExpressions, Dictionary<SqlString, SqlString> columnToAlias, bool switchOrderDirection) { SqlString[] result = new SqlString[sortExpressions.Length]; for (int i = 0; i < sortExpressions.Length; i++) { SqlString sortExpression = RemoveSortOrderDirection(sortExpressions[i]); if (columnToAlias.ContainsKey(sortExpression)) { sortExpression = columnToAlias[sortExpression]; } if (sortExpressions[i].Trim().EndsWithCaseInsensitive("desc")) { result[i] = new SqlString(sortExpression + (switchOrderDirection ? " ASC" : " DESC")); } else { result[i] = new SqlString(sortExpression + (switchOrderDirection ? " DESC" : " ASC")); } } return result; } ////////////////////////////////////////////////////////////////////////// // 以下所有方法复制自MsSql2000Dialect或MsSql2005Dialect源码 /// <summary> /// 获取From语句位置 /// </summary> /// <param name="querySqlString"></param> /// <returns></returns> private static int GetFromIndex(SqlString querySqlString) { string subselect = querySqlString.GetSubselectString().ToString(); int fromIndex = querySqlString.IndexOfCaseInsensitive(subselect); if (fromIndex == -1) { fromIndex = querySqlString.ToString().ToLowerInvariant().IndexOf(subselect.ToLowerInvariant()); } return fromIndex; } /// <summary> /// 获取select后插入点 /// </summary> /// <param name="sql"></param> /// <returns></returns> private static int GetAfterSelectInsertPoint(SqlString sql) { if (sql.StartsWithCaseInsensitive("select distinct")) { return 15; } else if (sql.StartsWithCaseInsensitive("select")) { return 6; } return 0; } /// <summary> /// 返回排序语句中的排序字段 /// </summary> /// <param name="sortExpression"></param> /// <returns></returns> private static SqlString RemoveSortOrderDirection(SqlString sortExpression) { SqlString trimmedExpression = sortExpression.Trim(); if (trimmedExpression.EndsWithCaseInsensitive("asc")) return trimmedExpression.Substring(0, trimmedExpression.Length - 3).Trim(); if (trimmedExpression.EndsWithCaseInsensitive("desc")) return trimmedExpression.Substring(0, trimmedExpression.Length - 4).Trim(); return trimmedExpression.Trim(); } /// <summary> /// 解析出字段名和别名 /// </summary> /// <param name="select"></param> /// <param name="columnsOrAliases"></param> /// <param name="columnToAlias"></param> private static void ExtractColumnOrAliasNames(SqlString select, out List<SqlString> columnsOrAliases, out Dictionary<SqlString, SqlString> columnToAlias) { columnsOrAliases = new List<SqlString>(); columnToAlias = new Dictionary<SqlString, SqlString>(); IList<string> tokens = new QuotedAndParenthesisStringTokenizer(select.ToString()).GetTokens(); int index = 0; while (index < tokens.Count) { string token = tokens[index]; index += 1; if ("select".Equals(token, StringComparison.InvariantCultureIgnoreCase)) { continue; } if ("distinct".Equals(token, StringComparison.InvariantCultureIgnoreCase)) { continue; } if ("," == token) { continue; } if ("from".Equals(token, StringComparison.InvariantCultureIgnoreCase)) { break; } //handle composite expressions like 2 * 4 as foo while (index < tokens.Count && "as".Equals(tokens[index], StringComparison.InvariantCultureIgnoreCase) == false && "," != tokens[index]) { token = token + " " + tokens[index]; index += 1; } string alias = token; bool isFunctionCallOrQuotedString = token.Contains("'") || token.Contains("("); // this is heuristic guess, if the expression contains ' or (, it is probably // not appropriate to just slice parts off of it if (isFunctionCallOrQuotedString == false) { int dot = token.IndexOf('.'); if (dot != -1) { alias = token.Substring(dot + 1); } } // notice! we are checking here the existence of "as" "alias", two // tokens from the current one if (index + 1 < tokens.Count && "as".Equals(tokens[index], StringComparison.InvariantCultureIgnoreCase)) { alias = tokens[index + 1]; index += 2; //skip the "as" and the alias \ } columnsOrAliases.Add(new SqlString(alias)); columnToAlias[SqlString.Parse(token)] = SqlString.Parse(alias); } } /// <summary> /// This specialized string tokenizier will break a string to tokens, taking /// into account single quotes, parenthesis and commas and [ ] /// Notice that we aren't differenciating between [ ) and ( ] on purpose, it would complicate /// the code and it is not legal at any rate. /// </summary> public class QuotedAndParenthesisStringTokenizer : IEnumerable<String> { private readonly string original; public QuotedAndParenthesisStringTokenizer(string original) { this.original = original; } IEnumerator<string> IEnumerable<string>.GetEnumerator() { StringBuilder currentToken = new StringBuilder(); TokenizerState state = TokenizerState.WhiteSpace; int parenthesisCount = 0; bool escapeQuote = false; for (int i = 0; i < original.Length; i++) { char ch = original[i]; switch (state) { case TokenizerState.WhiteSpace: if (ch == '\'') { state = TokenizerState.Quoted; currentToken.Append(ch); } else if (ch == ',') { yield return ","; } else if (ch == '(' || ch == '[') { state = TokenizerState.InParenthesis; currentToken.Append(ch); parenthesisCount = 1; } else if (char.IsWhiteSpace(ch) == false) { state = TokenizerState.Token; currentToken.Append(ch); } break; case TokenizerState.Quoted: if (escapeQuote) { escapeQuote = false; currentToken.Append(ch); } // handle escaping of ' by using '' or \' else if (ch == '\\' || (ch == '\'' && i + 1 < original.Length && original[i + 1] == '\'')) { escapeQuote = true; currentToken.Append(ch); } else if (ch == '\'') { currentToken.Append(ch); yield return currentToken.ToString(); state = TokenizerState.WhiteSpace; currentToken.Length = 0; } else { currentToken.Append(ch); } break; case TokenizerState.InParenthesis: if (ch == ')' || ch == ']') { currentToken.Append(ch); parenthesisCount -= 1; if (parenthesisCount == 0) { yield return currentToken.ToString(); currentToken.Length = 0; state = TokenizerState.WhiteSpace; } } else if (ch == '(' || ch == '[') { currentToken.Append(ch); parenthesisCount += 1; } else { currentToken.Append(ch); } break; case TokenizerState.Token: if (char.IsWhiteSpace(ch)) { yield return currentToken.ToString(); currentToken.Length = 0; state = TokenizerState.WhiteSpace; } else if (ch == ',') // stop current token, and send the , as well { yield return currentToken.ToString(); currentToken.Length = 0; yield return ","; state = TokenizerState.WhiteSpace; } else if (ch == '(' || ch == '[') { state = TokenizerState.InParenthesis; parenthesisCount = 1; currentToken.Append(ch); } else if (ch == '\'') { state = TokenizerState.Quoted; currentToken.Append(ch); } else { currentToken.Append(ch); } break; default: throw new InvalidExpressionException("Could not understand the string " + original); } } if (currentToken.Length > 0) { yield return currentToken.ToString(); } } public IEnumerator GetEnumerator() { return ((IEnumerable<string>)this).GetEnumerator(); } public enum TokenizerState { WhiteSpace, Quoted, InParenthesis, Token } public IList<string> GetTokens() { return new List<string>(this); } } } }
使用的时候,如下面代码所示,其中已包括分页方法3中对最后一页的处理
/// <summary> /// 分页获取多条数据(必须排序) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="session"></param> /// <param name="obj"></param> /// <param name="order"></param> /// <param name="start"></param> /// <param name="limit"></param> /// <param name="pk"></param> /// <param name="count"></param> /// <returns></returns> protected ICriteria CreateCriteria<T>(ISession session, T obj, Order order, int start, int limit, string pk, out int count) where T : class { ICriteria criteria = session.CreateCriteria<T>(); if (obj != null) criteria.Add(Example.Create(obj).ExcludeNone().ExcludeNulls()); ICriteria projCriteria = (ICriteria)criteria.Clone(); count = projCriteria.SetProjection(Projections.Count(pk)).UniqueResult<int>(); criteria.AddOrder(order); if (start + limit <= count) //完全在范围内 { criteria.SetFirstResult(start); criteria.SetMaxResults(limit); } else { if (start < count) //在最后一页 { criteria.SetFirstResult(start); criteria.SetMaxResults(count % limit); } else //完全在范围外 { criteria.SetMaxResults(0); } } return criteria; }