Oracle 10g分页使用'rownum'和NHibernate返回错误的结果集

时间:2012-11-08 15:34:45

标签: oracle nhibernate paging

目前,在使用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行完全丢失了。

所以,我想,到底是什么?

4 个答案:

答案 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会生成一个查询:

  1. 订购结果
  2. 将这些限制为前20个有序行
  3. 忘记订购......
  4. ...然后获取无序结果11-20。
  5. 最终结果是,您将看到每个页面的正确行数,但某些页面的行显示在其他页面上,并且也会丢失行。我已经手动对Oracle运行了NHibernate(4.0.3)生成的查询,以验证所有这些都是真的。

    我已经向NHibernate here提交了错误报告和补丁。希望我的补丁是犹太洁食,他们会在下一个版本中修复它。