如何在集成测试中自动关闭来自 IIS 的 SQL Server 连接以进行数据库重建
我正在通过 WinForms 和 WebService 处理一些数据库连接,我发现使用 Web 服务时连接不会关闭。这导致我的自动化测试出现故障,因为我在测试之间销毁并重新创建数据库,因此每个测试总是具有相同的起始状态。
在这两种情况下,调用的实际数据访问代码是相同的,例如:
using (DataHandler handler = new DataHandler(Config.ConnectionString()))
{
return handler.GetDbVersion();
}
DataHandler 使用数据上下文(使用 SqlMetal 创建)从 DB 表加载整数并返回它。
DataHandler 的 Dispose 方法关闭连接和数据上下文:
public void Dispose()
{
_data.Connection.Close();
_data.Connection.Dispose();
_data.Dispose();
}
当直接从单元测试运行时 - 运行完毕后数据库上就没有连接了。
select spid, status, loginame, hostname, blocked, db_name(dbid), cmd from master..sysprocesses where db_name(dbid) = 'TEST_DATABASE'
然后我将其包装在 [WebMethod] 中并将其放在 IIS7 实例上:
[WebMethod]
public int GetDatabaseVersionNumber()
{
using (DataHandler handler = new DataHandler(Config.ConnectionString()))
{
return handler.GetDbVersion();
}
}
测试调用 Web 服务:
int dbVersion = _webService.GetDatabaseVersionNumber();
Web 服务创建数据上下文 - 获取号码 - 调用 Dispose,然后返回号码。然而,连接在数据库中仍然处于“睡眠”状态。这意味着当我尝试销毁并重新创建数据库以进行下一个测试时,删除数据库失败(活动连接)。
select spid, status, loginame, hostname, blocked, db_name(dbid), cmd from master..sysprocesses where db_name(dbid) = 'TEST_DATABASE'
54 sleeping IIS APPPOOL\ASP.NET v4.0 PETE 0 TEST_DATABASE AWAITING COMMAND
我假设这是 IIS 和 SQL Server 处理的某种连接池,以减少来自 Web 请求的所有连接打开和关闭的开销(我想我不想在部署webservice)
但是,对于测试,我想终止此连接,以便我可以重建我的数据库。
处理这个问题的最佳方法是什么? (这是我需要在 IIS 中还是在测试数据库中设置的内容?我可以在连接字符串中定义它,以便仅在测试时发生吗?)
有关信息
通过将以下代码添加到测试拆卸方法来解决:
if (data.DatabaseExists())
{
data.ExecuteCommand("ALTER DATABASE TEST_DATABASE SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
data.ExecuteCommand("ALTER DATABASE TEST_DATABASE SET MULTI_USER;");
data.DeleteDatabase();
}
更新
我发现我可以通过将“Pooling=false”添加到单元测试 App.Config 中的连接字符串来绕过此问题。这似乎比强制关闭连接要好得多,因为当您强制关闭连接时,有时数据库无法用于套件中的下一个测试(您会收到“管道另一端没有服务”类型错误,因为 SQL已经消失,但 .net 认为它仍然存在)。
I'm playing about with some DB connections via WinForms and a WebService and I've found that when using the web service the connections are not closed. This is causing a glitch in my automated test because I destroy and recreate the database bewteen tests so I always have the same starting state for each test.
The actual data access code for the call is the same in both cases, for example:
using (DataHandler handler = new DataHandler(Config.ConnectionString()))
{
return handler.GetDbVersion();
}
The DataHandler uses a data context (created using SqlMetal) to load an integer from a DB table and return it.
The Dispose method of the DataHandler closes up the connection and data context:
public void Dispose()
{
_data.Connection.Close();
_data.Connection.Dispose();
_data.Dispose();
}
When running directly from the unit test - this runs through and afterwards there are no connections left on the DB.
select spid, status, loginame, hostname, blocked, db_name(dbid), cmd from master..sysprocesses where db_name(dbid) = 'TEST_DATABASE'
Then I wrap this in a [WebMethod] and put it on an IIS7 instance:
[WebMethod]
public int GetDatabaseVersionNumber()
{
using (DataHandler handler = new DataHandler(Config.ConnectionString()))
{
return handler.GetDbVersion();
}
}
And the test calls the web serivce:
int dbVersion = _webService.GetDatabaseVersionNumber();
The web service creates the data context - gets the number - calls Dispose and then returns the number. However the connection is still 'Sleeping' in the DB. This means that when I try to destroy and recreate the database for the next test, the drop database fails (Active connection).
select spid, status, loginame, hostname, blocked, db_name(dbid), cmd from master..sysprocesses where db_name(dbid) = 'TEST_DATABASE'
54 sleeping IIS APPPOOL\ASP.NET v4.0 PETE 0 TEST_DATABASE AWAITING COMMAND
I'm assuming this is some kind of connection pooling that IIS and SQL Server handle to reduce the overhead of all the opening and closing of connections from web requests (I guess I wouldn't want to mess with this in the context of deploying a webservice)
However, for the tests, I would like to kill off this connection so I can rebuild my database.
What is the best way to handle this? (Is this something I need to set in IIS, or in the test db? Can I define it in the connection string so it happens on test only?)
For info
Solved by adding the following code to the test teardown method:
if (data.DatabaseExists())
{
data.ExecuteCommand("ALTER DATABASE TEST_DATABASE SET SINGLE_USER WITH ROLLBACK IMMEDIATE;");
data.ExecuteCommand("ALTER DATABASE TEST_DATABASE SET MULTI_USER;");
data.DeleteDatabase();
}
Update
I found that I can bypass this issue by adding "Pooling=false" to the connection string in the Unit Test App.Config. This seems to work much better than force closing the connections because when you force close them sometimes the db isn't avaliable for the next test int he suite (you get a "no serice on the other end of the pipe" type error because SQL has gone away but .net thinks it's still there).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您始终可以运行更改数据库命令并将其设置为单用户并立即回滚。这应该会终止您删除数据库或任何您想要的任务的所有连接。
You could always run an alter database command and set it to single user with rollback immediate. That should kill all coonections for you to delete the database, or whichever task you want.
检查使用 SQL Server 连接池其中指出
Check Using Connection Pooling with SQL Server which states