Удаление таблиц фактически не выполняется Firebird до закрытия приложения

У меня есть тестовое приложение.net, которое создает таблицы, использует их, а затем отбрасывает их много раз.

Когда я запускаю это приложение для базы данных Firebird 3.0.2 с помощью Firebird ADO.Net Data Provider 5.9.1, оно перестает работать, как только ему нужно создать таблицу с тем же именем, что и ранее удаленная: таблица уже существует!

Перезапуск приложения уворачивается от неприятностей, но я не хочу перезапускать его после каждого теста.

Этот вопрос очень похож, но вместо этого зависает и использует напрямую Firebird isql инструмент вместо.Net приложения.

Есть ли способ фактически удалить таблицы в Firebird без перезапуска приложения?

Это приложение тестирует многие другие базы данных, без этой проблемы: SQL-Server, SQL-Server Compact Edition, MySql, SQLite, Oracle, PostgreSQL.

Вот MCVE, терпящий неудачу для Firebird. Замените две первые строки подходящим кодом для наличия строки подключения. Весь остальной код - это только поставщик данных 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();
        }
    }
}

Только выбора из таблицы было недостаточно. Беда появилась только после того, как я добавил удаление в тест. Сбой при последней фиксации транзакции, той, которая была отброшена, с сообщением:

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()

По словам Натана Брауна в дискуссии на Github, эта проблема кажется ограниченной только провайдеру Firebird ADO.Net Data. Он сузил его до перехода с версии 2.7.7 на 3.0.0.

1 ответ

Решение

Похоже, что удаление таблиц задерживается до тех пор, пока соединения, которые их использовали, не будут фактически закрыты, а не только возвращены в пул. Очистка пула соединений приводит к тому, что эти отложенные отбрасывания выполняются.

Таким образом, добавление следующего кода перед удалением таблицы решает эту проблему в моем случае:

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

Это решение было найдено здесь в изолированном тесте среди более чем 5 000.

Похоже, есть еще один вариант, позвонив 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);
}
Другие вопросы по тегам