NHibernate 认为按列排序不在 select 中

NHibernate thinks order by column isn't in select

我正在尝试获取查询结果的第 2 页,这是准备好的语句 SQL 查询我的代码构建:

SELECT DISTINCT Contents.*
FROM Contents
INNER JOIN ContentsFilter ON ContentsFilter.ContentId = Contents.ContentId
INNER JOIN Filter ON Filter.Id = ContentsFilter.FilterId
WHERE Contents.[Key] LIKE 'kh_%'
AND Filter.Name IN (:filters)
ORDER BY Contents.Created DESC

然后对其进行参数化等,并在构造的 IQuery 上调用 SetFetchSize()SetFirstResult()。在第 1 页上,这工作正常,但在第 2 页上出现此异常:

NHibernate.HibernateException: The dialect was unable to perform paging of a statement that requires distinct results, and is ordered by a column that is not included in the result set of the query.
   at NHibernate.Dialect.MsSql2005DialectQueryPager.BuildFromClauseForPagingDistinctQuery(MsSqlSelectParser sqlQuery, SqlStringBuilder result)
   at NHibernate.Dialect.MsSql2005DialectQueryPager.PageByLimitAndOffset(SqlString offset, SqlString limit)
   at NHibernate.Dialect.MsSql2005DialectQueryPager.PageBy(SqlString offset, SqlString limit)
   at NHibernate.Dialect.MsSql2005Dialect.GetLimitString(SqlString queryString, SqlString offset, SqlString limit)
   at NHibernate.Dialect.Dialect.GetLimitString(SqlString queryString, Nullable`1 offset, Nullable`1 limit, Parameter offsetParameter, Parameter limitParameter)
   at NHibernate.Loader.Loader.TryGetLimitString(Dialect dialect, SqlString queryString, Nullable`1 offset, Nullable`1 limit, Parameter offsetParameter, Parameter limitParameter, SqlString& result)
   at NHibernate.Loader.Loader.AddLimitsParametersIfNeeded(SqlString sqlString, ICollection`1 parameterSpecs, QueryParameters queryParameters, ISessionImplementor session)
   at NHibernate.Loader.Loader.CreateSqlCommand(QueryParameters queryParameters, ISessionImplementor session)
   at NHibernate.Loader.Loader.PrepareQueryCommand(QueryParameters queryParameters, Boolean scroll, ISessionImplementor session)
   at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies, IResultTransformer forcedResultTransformer)
   at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies, IResultTransformer forcedResultTransformer)
   at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters, IResultTransformer forcedResultTransformer)
   at NHibernate.Loader.Loader.DoList(ISessionImplementor session, QueryParameters queryParameters)
   at NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters)
   at NHibernate.Loader.Loader.List(ISessionImplementor session, QueryParameters queryParameters, ISet`1 querySpaces, IType[] resultTypes)
   at NHibernate.Loader.Custom.CustomLoader.List(ISessionImplementor session, QueryParameters queryParameters)
   at NHibernate.Impl.SessionImpl.ListCustomQuery(ICustomQuery customQuery, QueryParameters queryParameters, IList results)
   at NHibernate.Impl.AbstractSessionImpl.List(NativeSQLQuerySpecification spec, QueryParameters queryParameters, IList results)
   at NHibernate.Impl.AbstractSessionImpl.List(NativeSQLQuerySpecification spec, QueryParameters queryParameters)
   at NHibernate.Impl.SqlQueryImpl.List()
   at MySite.Data.NHibernate.Shell.KnowledgeHubRepository.ExecuteKnowledgeHubQuery(IQuery query) in C:\Work\MySite\src\app\MySite.Data.NHibernate\Shell\KnowledgeHubRepository.cs:line 194
   at MySite.Data.NHibernate.Shell.KnowledgeHubRepository.FindByFilter(String[] filters, Int32 page) in C:\Work\MySite\src\app\MySite.Data.NHibernate\Shell\KnowledgeHubRepository.cs:line 174
   at Castle.Proxies.Invocations.IKnowledgeHubRepository_FindByFilter.InvokeMethodOnTarget()
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at MySite.Core.App.Ioc.Interceptors.StopwatchInterceptor.Intercept(IInvocation invocation) in C:\Work\MySite\src\app\MySite.Core\App\Ioc\Interceptors\StopwatchInterceptor.cs:line 11
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.IKnowledgeHubRepositoryProxy.FindByFilter(String[] filters, Int32 page)
   at MySite2.Web.Controllers.KnowledgeHubController.Get(Int32 page, String filters) in C:\Work\MySite\src\app\Website\Controllers\KnowledgeHubController.cs:line 119
   at lambda_method(Closure , ControllerBase , Object[] )
   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
   at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
   at MySite.Core.App.Ioc.WindsorActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) in C:\Work\MySite\src\app\MySite.Core\App\Ioc\WindsorActionInvoker.cs:line 21
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)

我非常清楚地选择了 * 所以我认为异常消息可能是不正确的,如果我将其更改为 SELECT DISTINCT Contents.*, Contents.Created 果然我得到 System.Data.SqlClient.SqlException: The column 'Created' was specified multiple times for 'q_'.

那么这个错误消息到底是什么意思?

我正在使用 NHibernate 4.0.4.4000 和 SQL Server Express 2008 64 位 10.0.6。

我猜你使用 ISession.CreateSqlQuery。我什至不知道 NHibernate 会尝试解析任意 SQL 查询以在其中注入分页,但它确实做到了。

很遗憾,您的案例不在承保范围内。第一页之所以有效,是因为它不必应用偏移量,只需在 select 之后插入一个 top 语句即可。这是一个非常简单的 'parse and alter the query' 案例,不需要执行很多检查。请参阅 MsSql2005DialectQueryPager.cs 中的 PageByLimitOnly

第二页和下一页被吹走了,因为它必须注入一个 row_number() over (order by yourOrderBy) where 条件语句来抵消 SQL Server 2008/2005,这相当复杂。
NHibernate 必须重写 order by 并且当前的实现通过依赖于 distinct 中不存在的列来防止重写无效的顺序。旧 SQL 服务器版本支持按未 selected 的列排序的不同查询,这种情况会导致 SQL Server 2005/2008 的 NHibernate 分页逻辑失败。 (SQL Server 2000 支持这种不同的情况,但我认为它在 SQL Server 2005 中被删除了,所以 NHibernate 确实这样做可能还有另一个原因。)
但是当前执行的检查没有考虑 *。 请参阅 MsSql2005DialectQueryPager.cs 中的 BuildFromClauseForPagingDistinctQuery

很好,既然您已经提供了自己的 SQL,为什么不在其中插入您自己的分页语句呢?

否则,您需要尝试通过 NHibernate 上的拉取请求来支持您的案例,并等待其合并和发布。

或者可能升级到 SQL 2012 并将 NHibernate 方言设置为 MsSql2012Dialect:它支持 offset fetch SQL 语句,更容易注入任意 SQL 查询.