在应用程序关闭之前,Firebird实际上并未完成表格

时间:2017-06-04 11:33:07

标签: .net firebird firebird-3.0

我有一个.net测试应用程序,它创建表,使用它们,然后多次删除它们。

当我使用Firebird ADO.Net Data Provider 5.9.1对Firebird 3.0.2数据库运行此应用程序时,它很快就会失败,因为它必须创建一个与之前删除的名称相同的表:table已经存在!

重新启动应用程序避免了麻烦,但我不希望在每次测试后重新启动它。

这个question非常相似,但却悬而未决,直接使用Firebird isql工具而不是.Net应用程序。

有没有办法在不重新启动应用程序的情况下实际删除Firebird中的表?

此应用程序测试许多其他数据库,没有此问题:SQL-Server,SQL-Server Compact Edition,MySql,SQLite,Oracle,PostgreSQL。

这是针对Firebird的MCVE失败。用适当的代码替换两个第一行以获得连接字符串。所有其他代码只是Firebird ADO.Net数据提供程序和NUnit。它并没有像我的实际应用那样完全失败,但我认为这是同样的潜在问题。

[Test]
public void CreateSelectDrop()
{
    var cfg = TestConfigurationHelper.GetDefaultConfiguration();
    var cnxStr = cfg.Properties[Environment.ConnectionString];
    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "create table test (id int not null primary key)";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "insert into test (id) values (1)";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "select id from test";
                using (var reader = cmd.ExecuteReader())
                {
                    Assert.IsTrue(reader.Read());
                    Assert.AreEqual(1, reader.GetInt32(0));
                    Assert.IsFalse(reader.Read());
                }
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "delete from test";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }

    using (var cnx = new FbConnection(cnxStr))
    {
        cnx.Open();
        using (var tran = cnx.BeginTransaction())
        {
            using (var cmd = cnx.CreateCommand())
            {
                cmd.Transaction = tran;
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "drop table test";
                cmd.ExecuteNonQuery();
            }
            tran.Commit();
        }
    }
}

仅从表中选择是不够的。只有在我将删除添加到测试后才会出现问题。它在最后一次事务提交时失败,这是一个drop,带有消息:

FirebirdSql.Data.FirebirdClient.FbException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
  ----> FirebirdSql.Data.Common.IscException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
   at FirebirdSql.Data.FirebirdClient.FbTransaction.Commit()
   at NHibernate.Test.DialectTest.FirebirdDialectFixture.CreateSelectDrop()

根据Nathan Brown in a Github discussion,这个问题似乎仅限于Firebird ADO.Net数据提供商。他已将其缩小为从2.7.7版本切换到3.0.0。

1 个答案:

答案 0 :(得分:2)

似乎表的丢弃被延迟,直到使用它们的连接实际关闭,不仅返回到池中。清除连接池会导致实际完成延迟丢弃。

因此,在表格删除之前添加类似下面的代码之类的东西解决了我的问题:

using (var connection = GetConnection())
{
    FbConnection.ClearPool(connection);
}

在超过5000个的孤立测试中发现此解决方案here

看起来还有另一个选项,而是调用FbConnection.ClearAllPool()。虽然我没有检查过,但前者很可能只清除连接池中提供的连接连接字符串,而后者清除所有连接的池,而不管它们的连接字符串是什么。

由于它是一个具有一些通用逻辑和一些特殊性的测试应用程序,我将实际用作解决方案的代码将是:

// Firebird will pool each connection created during the test and will 
// marked as used any table referenced by queries. It will delays those
// tables drop until connections are actually closed.
// This results in other tests failing when they try to create tables with
// same name.
// By clearing the connection pool the tables will get dropped. This is done
// by the following code.
// Moved from NH1908 test case, contributed by Amro El-Fakharany.
var clearConnection = Sfi.ConnectionProvider.GetConnection();
try
{
    var fbConnectionType = clearConnection.GetType();
    var clearPool = fbConnectionType.GetMethod("ClearPool");
    clearPool.Invoke(null, new object[] {clearConnection});
}
finally
{
    Sfi.ConnectionProvider.CloseConnection(clearConnection);
}