【问题标题】:NullReference when disposing session with transaction使用事务处理会话时的 NullReference
【发布时间】:2015-01-14 19:58:19
【问题描述】:

通常它工作正常,但在某些情况下(我无法重现它)我收到带有堆栈跟踪的 NullReferenceException:

在 C:\projects\Npgsql2\src\Npgsql\NpgsqlCommand.cs:line 1505 中的 Npgsql.NpgsqlCommand.ClearPoolAndCreateException(Exception e) 在 C:\projects\Npgsql2\src\Npgsql\NpgsqlCommand.cs:line 650 中的 Npgsql.NpgsqlCommand.GetReader(CommandBehavior cb) 在 C:\projects\Npgsql2\src\Npgsql\NpgsqlCommand.cs:line 499 中的 Npgsql.NpgsqlCommand.ExecuteBlind() 在 C:\projects\Npgsql2\src\Npgsql\NpgsqlTransaction.cs:line 185 中的 Npgsql.NpgsqlTransaction.Rollback() 在 C:\projects\Npgsql2\src\Npgsql\NpgsqlTransaction.cs:line 141 中的 Npgsql.NpgsqlTransaction.Dispose(Boolean disposing) 在 NHibernate.Transaction.AdoTransaction.Dispose(Boolean isDisposing) 在 p:\nhibernate-core\src\NHibernate\Transaction\AdoTransaction.cs:line 368 在 NHibernate.Impl.SessionImpl.Close() 在 p:\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:line 380 在 NHibernate.Impl.SessionImpl.Dispose(Boolean isDisposing) 在 p:\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:line 1738 在 NHibernate.Impl.SessionImpl.Dispose() 在 p:\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:line 1709
    public virtual IEnumerable<User.PublishedInfo> GetUsersByXP(int count)
    {
        using (ISession session = SessionFactory.OpenSession())
        {
            using (session.BeginTransaction())
            {
                var result = session.CreateCriteria<User>()
                    .SetProjection(PublishedUserProjections)
                    .AddOrder(Order.Desc("XP"))
                    .SetMaxResults(count)
                    .SetResultTransformer(Transformers.AliasToBean<User.PublishedInfo>())
                    .List<User.PublishedInfo>();

                foreach (var r in result)
                    r.Initialize();

                session.Transaction.Commit();

                return result; // this line
            }
        }
    }

这里可能有什么问题?

更新

有时在同一个代码片段中(但在 BeginTransaction 中)我收到异常“获取连接时超时”。也许这有某种关联。

【问题讨论】:

  • 可能是因为有时会话中的事务根本没有初始化,然后内部带有session.BeginTransaction()using 块试图处理实际上不存在的东西?简单地说,我会尝试在这个块中初始化一个事务变量,并且只有当它不为空时才提交它。此外,当发生事务以外的错误时会发生什么?我在任何地方都看不到任何回滚...
  • @WillMarcouiller “当发生交易以外的错误时会发生什么?”我不在乎,因为只有读取操作。 “有时会话中的事务根本没有初始化” - 它应该在进入 using 块之前引发异常。 “仅当它不为空时才提交。” NullReference 不在我的代码中。它在调用 ISession.Dispose 时被抛出。
  • 我明白了。但是,如果它只读取,为什么首先调用BeginTransaction 方法?如果我相信方法的名称,我会认为它只从底层数据库中读取,正如GetUsersByXP 所说。这里不需要交易。另外,实践是分配一个变量,该变量应在 using 块中处理,然后 .NET 确切地知道要处理什么。在这里,正如我所看到的,我猜想在处理会话时存在某种混淆,因为已调用 BeginTransaction 方法并且事务中没有保留任何特定引用。
  • “为什么首先调用 BeginTransaction 方法?”因为有行规和同步。 “事务中没有特定的引用” 引用是由编译器隐式创建的,否则它不知道应该调用哪个对象 Dispose。它只是一个语法糖。

标签: c# postgresql nhibernate connection-pooling npgsql


【解决方案1】:

我在 2.0.14.3 中看到了同样的情况。你运行的是哪个版本?以及哪个版本的 NHibernate?

查看代码,我明白了

    internal NpgsqlException ClearPoolAndCreateException(Exception e)
    {
        Connection.ClearPool();
        return new NpgsqlException(resman.GetString("Exception_ConnectionBroken"), e);
    }

2.2 似乎也有同样的遭遇

大多数其他操作总是对 Connection 属性进行空检查。这可能是一个错误,但它似乎也在 master 分支中被大量重写。

我能找到的唯一一个实际上会在内部将连接设置为 null 的来源(除非它是由 NHibernate 以某种方式完成的,但我对此表示怀疑)是 NpgsqlCommand 是否会被释放然后从 NpgsqlDataReader 读取。

编辑:

根据来源,如果 NpgsqlTransaction 仍然有连接并且事务处于活动状态,则会在 Dispose 上调用 Rollback。在您的情况下,提交应该将其设置为空。您在 using 块中使用 session.Transaction 的事实(与存储从 BeginTransaction() 调用返回的引用相反)可能是罪魁祸首,因为如果会话中不存在对 session.Transaction 的调用,则会隐式创建事务.这可能导致提交调用实际上只提交一个空事务,然后将在使用 BeginTransaction 创建的事务上调用 Dispose。

您是否有任何多线程或异步代码可能在此代码运行时与您的会话一起工作?

【讨论】:

  • 它根本不应该调用 Rollback(参见我的 stracktrace),因为事务已经提交。
  • Npgsql 是 2.0.13.91。如您所见,会话没有传递到外部,因此不会涉及多线程。
【解决方案2】:

在我看来,如果事务内部发生了一些错误,它会抛出异常并处理会话/事务。试试……

    using (ISession session = SessionFactory.OpenSession())
    {
        List<User.PublishedInfo> results = null;
        ITransaction transaction = null;
        try
        {
            transaction = session.BeginTransaction();
            results = session.CreateCriteria<User>()
                .SetProjection(PublishedUserProjections)
                .AddOrder(Order.Desc("XP"))
                .SetMaxResults(count)
                .SetResultTransformer(Transformers.AliasToBean<User.PublishedInfo>())
                .List<User.PublishedInfo>();

            foreach (var r in results)
                r.Initialize();

            transaction.Commit();
        }
        catch(Exceptionn ex)
        {
            if (transaction != null)
                transaction.Rollback();
            throw;
        }
        return results;
    }

【讨论】:

  • 我添加了“throw new Exception();”在 transaction.Commit() 之前到我的代码,但我无法重现 NullReferenceException。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-20
  • 2013-01-21
  • 2015-01-14
相关资源
最近更新 更多