Я пытаюсь создать временную базу данных с целью интеграционного тестирования в Xunit, но когда я пытаюсь удалить временную базу данных, я получаю сообщение об ошибке:
Невозможно удалить базу данных "TempDatabase_[numbers]", так как она в настоящее время используется.
Простое закрытие и удаление команды и соединения, похоже, не помогает.
Это урезанная версия моего теста, который не работает:
using System;
using System.Data.SqlClient;
using Xunit;
namespace Test
{
public class Test_Raw_Spec
{
[Fact]
public void PerformWorkInTemporaryDatabase()
{
string connectionStringTemplate = "Data Source=SQLEXPRESS;Initial Catalog = {0};Integrated Security=SSPI;Connection Timeout=10";
int dbNum = (new Random()).Next() % 1000000;
int tblNum = (new Random()).Next() % 1000000;
string nameTempDb = $"TempDatabase_{dbNum}";
string nameTempTable = $"TempTable_{tblNum}";
var sqlConnection1 = new SqlConnection(string.Format(connectionStringTemplate, "master"));
var sqlCommand1 = new SqlCommand($"CREATE DATABASE {nameTempDb}", sqlConnection1);
sqlConnection1.Open();
sqlCommand1.ExecuteNonQuery();
sqlCommand1.Dispose();
sqlConnection1.Close();
sqlConnection1.Dispose();
var sqlConnection2 = new SqlConnection(string.Format(connectionStringTemplate, nameTempDb));
var sqlCommand2 = new SqlCommand($"CREATE TABLE {nameTempTable}(id int)", sqlConnection2);
sqlConnection2.Open();
sqlCommand2.ExecuteNonQuery();
sqlCommand2.Dispose();
sqlConnection2.Close();
sqlConnection2.Dispose();
var sqlConnection3 = new SqlConnection(string.Format(connectionStringTemplate, "master"));
var sqlCommand3 = new SqlCommand($"DROP DATABASE {nameTempDb}", sqlConnection3);
sqlConnection3.Open();
sqlCommand3.ExecuteNonQuery();
sqlCommand3.Dispose();
sqlConnection3.Close();
sqlConnection3.Dispose();
}
}
}
Вы пробовали немного подождать, прежде чем удалить БД? Вполне может быть, что соединение не успело вовремя закрыться.
Также вам не нужно закрывать соединение, вызов dispose все равно сделает все за вас. Плюс лучше всего сделать это в операторе using
using(var sqlConnection1 = new SqlConnection(string.Format(connectionStringTemplate, "master")))
{
//do your stuff here
}
Таким образом, вам не нужно беспокоиться о закрытии чего-либо, так как это будет сделано автоматически в конце блока использования.
Я также не рекомендовал бы открывать множество подключений. Одного соединения будет достаточно, так как открытие соединений стоит дорого. Поэтому создайте один (или два в вашем случае), а затем повторно используйте его для каждой команды.
Попробуйте вызвать sqlConnection2.ClearAllPools() перед удалением базы данных
Это из-за пула соединений. Когда вы закрываете и уничтожаете соединение, оно возвращается в пул, готовое к повторному использованию, а не уничтожается. Создание и удаление соединений снова и снова — очень затратный процесс, поэтому пулы соединений используются для повышения общей производительности вашего приложения. Соединения уничтожаются, когда они завершены (когда пул перезапускается или перезапускается, например, когда ваше приложение запускается или закрывается).
Кроме того, вы могли бы более эффективно использовать команды и соединения. Вы можете изменить текст команды, если она выполнена. Если вы не хотите этого делать, вы можете хотя бы повторно использовать соединение:
private void Execute()
{
using (var connection = new SqlConnection("."))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "CREATE DATABASE [test]";
command.ExecuteNonQuery();
connection.ChangeDatabase("test");
command.CommandText = "CREATE TABLE [dbo].[MyTable] (id int)";
command.ExecuteNonQuery();
// you must change your db context to drop the database
connection.ChangeDatabase("master");
command.CommandText = "DROP DATABASE [test]";
command.ExecuteNonQuery();
}
}
}
Пул реализован в библиотеке SqlClient. Вы получаете один пул для каждой уникальной строки подключения и не связаны с использованием разработчиками инструкции «using».
Да, вы правы. Я понял только после того, как набрал комментарий и удалил его...
ALTER DATABASE [test] SET SINGLE_USER WITH ROLLBACK AFTER 5
заставит все остальные соединения отключиться от базы данных через 5 секунд (например, другие соединения в пуле).
Я обнаружил, что вы также можете добавить Pooling=false к своей строке подключения, что в конечном итоге также очистит пул соединений (см. stackoverflow.com/questions/7004701/…). Поскольку предполагается, что интеграционное тестирование является меньшим подмножеством всех ваших тестов, это также было бы жизнеспособной альтернативой для меня.
Добавление 120-секундной задержки между соединениями 2 и 3 (System.Threading.Thread.Sleep(2 * 60 * 1000);), похоже, не помогает. Мне нравятся ваши другие предложения, и я реализую их, как только получу базовый уровень.