目前,在使用Oracle 10g和NHibernate分页数据时,我在应用程序中遇到了一些奇怪的行为。
我有一个包含20行的表,我想在列表中显示每个站点10行,所以我的列表有2页。
使用NHibernate我正在设置第一页query.SetMaxResults(10).SetFirstResult(0)
,
对于第二页query.SetMaxResults(10).SetFirstResult(10)
。
将为第一页创建以下SQL:
SELECT * FROM table WHERE rownum <= 10;
将为第二页创建以下SQL:
SELECT row_.*, rownum rownum_
FROM
(
SELECT *
FROM table
) row_
WHERE rownum <= 20 WHERE rownum_ > 10;
第一个查询正确返回前10行,而第二个查询只返回4个新行和6个已经在“第1页”中的行。所以6行完全丢失了。
所以,我想,到底是什么?
答案 0 :(得分:2)
您可以创建自己的自定义Oracle方言来解决此问题,也可以等待接受补丁NH-3814。最好等待NHibernate接受补丁,但如果你没有时间,你可以这样做。
https://nhibernate.jira.com/browse/NH-3814
此类将覆盖Oracle8iDialect中的方法,以允许rownum_字段用于skip和take值。我不得不使用反射来调用ExtractColumnOrAliasNames,因为这是一个私有方法。
internal class Oracle10gDialectExtended : NHibernate.Dialect.Oracle10gDialect
{
public Oracle10gDialectExtended()
{
}
/// <summary>
/// This is from patch NH-3814
/// https://nhibernate.jira.com/browse/NH-3814
/// https://nhibernate.jira.com/secure/attachment/24661/Oracle8iDialect.cs
/// </summary>
/// <param name="sql"></param>
/// <param name="offset"></param>
/// <param name="limit"></param>
/// <returns></returns>
public override NHibernate.SqlCommand.SqlString GetLimitString(NHibernate.SqlCommand.SqlString sql, NHibernate.SqlCommand.SqlString offset, NHibernate.SqlCommand.SqlString limit)
{
sql = sql.Trim();
bool isForUpdate = false;
if (sql.EndsWithCaseInsensitive(" for update"))
{
sql = sql.Substring(0, sql.Length - 11);
isForUpdate = true;
}
string selectColumns = ExtractColumnOrAliasNames(sql);
var pagingSelect = new SqlStringBuilder(sql.Count + 10);
if (offset != null)
{
pagingSelect.Add("select " + selectColumns + " from ( select row_.*, rownum rownum_ from ( ");
}
else
{
pagingSelect.Add("select " + selectColumns + " from ( ");
}
pagingSelect.Add(sql);
if (offset != null && limit != null)
{
pagingSelect.Add(" ) row_ ) where rownum_ <=").Add(limit).Add(" and rownum_ >").Add(offset);
}
else if (limit != null)
{
pagingSelect.Add(" ) where rownum <=").Add(limit);
}
else
{
// offset is specified, but limit is not.
pagingSelect.Add(" ) row_ ) where rownum_ >").Add(offset);
}
if (isForUpdate)
{
pagingSelect.Add(" for update");
}
return pagingSelect.ToSqlString();
}
private string ExtractColumnOrAliasNames(SqlString select)
{
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
MethodInfo info = typeof(NHibernate.Dialect.Oracle8iDialect).GetMethod("ExtractColumnOrAliasNames", flags, null, new Type[] { typeof(SqlString) }, null);
if (info == null)
{
throw new MissingMethodException("Method ExtractColumnOrAliasNames does not exist on class NHibernate.Dialect.Oracle8iDialect");
}
return info.Invoke(this, new object[] { select }) as string;
}
}
答案 1 :(得分:0)
也许我错过了一些东西,但我认为你想要第二个问题:
SELECT row_.*
FROM
(
SELECT *, rownum rn --- place your rownum here
FROM table
) row_
WHERE rn > 10
and rn <=20
这会将rownum
应用于内部查询,然后在WHERE
子句中选择行号为11-20的记录。
注意,您应该在两个查询中包含ORDER BY
子句,以确保返回时记录的顺序正确。
答案 2 :(得分:0)
NH 3.2在Oracle上存在分页问题(类似问题here)。我有同样的问题并继续使用3.1直到它被修复。
答案 3 :(得分:-1)
我觉得Powerslave胖了他的例子,最初包括命令,就像这样:
SELECT * FROM
(SELECT row_.*, rownum rownum_ FROM
(
SELECT *
FROM table
ORDER BY some_column
) row_
WHERE rownum <= 20)
WHERE rownum_ > 10;
基本上,NHibernate会生成一个查询:
最终结果是,您将看到每个页面的正确行数,但某些页面的行显示在其他页面上,并且也会丢失行。我已经手动对Oracle运行了NHibernate(4.0.3)生成的查询,以验证所有这些都是真的。
我已经向NHibernate here提交了错误报告和补丁。希望我的补丁是犹太洁食,他们会在下一个版本中修复它。