无法删除数据库,因为它目前正在使用。如何解决?

60

运行这段简单的代码后,我收到了"Cannot drop database "test_db" because it is currently in use"(在CleanUp方法中)的错误提示。

[TestFixture]
public class ClientRepositoryTest
{
    private const string CONNECTION_STRING = "Data Source=.;Initial Catalog=test_db;Trusted_Connection=True";
    private DataContext _dataCntx;

    [SetUp]
    public void Init()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>());
        _dataCntx = new DataContext(CONNECTION_STRING);
        _dataCntx.Database.Initialize(true);
    }

    [TearDown]
    public void CleanUp()
    {
        _dataCntx.Dispose();
        Database.Delete(CONNECTION_STRING);
    }
}

DataContext有一个类似这样的属性

 public DbSet<Client> Clients { get; set; }

如何强制代码删除数据库? 谢谢


CTP5已经过时。当前版本是EF 4.1。 - Ladislav Mrnka
8个回答

71
问题在于你的应用程序可能仍然持有数据库的某些连接(或者其他应用程序也持有连接)。在任何打开的连接存在时,数据库都无法被删除。第一个问题可以通过关闭连接池(在连接字符串中添加Pooling=false)或在删除数据库之前清除池(通过调用SqlConnection.ClearAllPools())来解决。
这两个问题都可以通过强制删除数据库来解决,但是需要自定义数据库初始化器,在其中将数据库切换到单用户模式,然后再将其删除。这里有一些示例可供参考。

4
不使用池化技术完成了这项工作。谢谢! - YMC
2
如果我将pooling设置为false并且已经设置了single_user,但仍然收到此错误消息,该怎么办? - ashes999

43

我对此感到非常困扰!我在SQL Server Management Studio (SSMS)中有一个打开的数据库连接,以及一个表查询,以查看一些单元测试的结果。当在Visual Studio中重新运行测试时,我希望它总是能够即使在SSMS中打开连接也可以删除数据库。

以下是消除因为当前正在使用无法删除数据库的明确方法:

Entity Framework Database Initialization

诀窍是在自定义Initializer中覆盖InitializeDatabase方法。

这里复制了相关部分,以达到良好的重复利用... :)

如果数据库已经存在,则可能会遇到出错的情况。异常"Cannot drop database because it is currently in use"可能会引发。当仍有一个活动连接保持与正在被删除的数据库连接时,就会出现此问题。一个技巧是覆盖InitializeDatabase方法并修改数据库。这告诉数据库关闭所有连接,并回滚该事务(如果有打开的事务)。

public class CustomInitializer<T> : DropCreateDatabaseAlways<YourContext>
{
    public override void InitializeDatabase(YourContext context)
    {
        context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction
            , string.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", context.Database.Connection.Database));

        base.InitializeDatabase(context);
    }

    protected override void Seed(YourContext context)
    {
        // Seed code goes here...

        base.Seed(context);
    }
}

1
这是自动化测试中删除用户最简单和最直接的方法。 - moribvndvs
2
加上 IF EXISTS 就完美了。 - ericosg
@ericosg 如何使用 IF EXISTS - Kiquenet
使用 Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\wingtiptoys.mdf;Integrated Security=True 可以用于 mdf 文件 吗? - Kiquenet
@Kiquenet 像这样 https://dev59.com/pXRB5IYBdhLWcg3wQFPu - ericosg

18

这是一个非常激进的数据库(重新)初始化器,用于EF Code-First与迁移。使用它需要谨慎,但在我看来,它似乎运行得相当可靠。它将会:

  1. 强制断开任何其他客户端与数据库的连接
  2. 删除数据库
  3. 使用迁移重建数据库,并运行Seed方法
  4. 需要很长时间!(请注意您测试框架的超时限制;默认60秒的超时可能不足够)

这是类;

public class DropCreateAndMigrateDatabaseInitializer<TContext, TMigrationsConfiguration>: IDatabaseInitializer<TContext> 
    where TContext: DbContext
    where TMigrationsConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<TContext>, new()
{
    public void InitializeDatabase(TContext context)
    {
        if (context.Database.Exists())
        {
            // set the database to SINGLE_USER so it can be dropped
            context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, "ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");

            // drop the database
            context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, "USE master DROP DATABASE [" + context.Database.Connection.Database + "]");
        }

        var migrator = new MigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>();
        migrator.InitializeDatabase(context);

    }
}

像这样使用:

public static void ResetDb()
{
    // rebuild the database
    Console.WriteLine("Rebuilding the test database");
    var initializer = new DropCreateAndMigrateDatabaseInitializer<MyContext, MyEfProject.Migrations.Configuration>();
    Database.SetInitializer<MyContext>initializer);

    using (var ctx = new MyContext())
    {
        ctx.Database.Initialize(force: true);
    }
}

我也使用Ladislav Mrnka的“Pooling=false”技巧,但我不确定它是否必需,或者只是一种保险措施。这肯定会导致测试速度变慢。


5

那些解决方案对我来说都不起作用。最后,我编写了一个扩展方法,它有效地解决了问题:

private static void KillConnectionsToTheDatabase(this Database database)
{
    var databaseName = database.Connection.Database;
    const string sqlFormat = @"
             USE master; 

             DECLARE @databaseName VARCHAR(50);
             SET @databaseName = '{0}';

             declare @kill varchar(8000) = '';
             select @kill=@kill+'kill '+convert(varchar(5),spid)+';'
             from master..sysprocesses 
             where dbid=db_id(@databaseName);

             exec (@kill);";

    var sql = string.Format(sqlFormat, databaseName);
    using (var command = database.Connection.CreateCommand())
    {
        command.CommandText = sql;
        command.CommandType = CommandType.Text;

        command.Connection.Open();

        command.ExecuteNonQuery();

        command.Connection.Close();
    }
}

我们可以使用 dbContext.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, sqlString) 代替 database.Connection.CreateCommand()。否则这是一个不错的解决方案。 - niaher
请帮我理解一下,database.Connection.CreateCommand() 有什么问题吗?我之前没有遇到过任何问题。谢谢! - Chris McKenzie
没有,这只是更冗长而已。 - niaher
如何使用它?Database.SetInitializer(new EventDatabaseInitializer()); - Kiquenet

2

我试着像Ladislav Mrnka说的那样添加Pooling=false,但总是出现错误。
我正在使用Sql Server Management Studio,即使我关闭了所有连接,仍然会出现错误。

如果我关闭Sql Server Management Studio,那么数据库就会被删除 :)
希望这可以帮助到您。


1
很可能您没有在“Sql Server Management Studio”中关闭所有连接,请注意,通常情况下您有两个连接正在运行,一个用于对象资源管理器,另一个用于当前查询。如果您关闭/切换它们两个,它应该可以正常工作。 - DrCopyPaste
1
正如@DrCopyPaste所说,SSMS非常积极地保持与您的数据库的连接。我还没有找到一个好的处理方法,除了关闭应用程序。 - Steve Cooper
1
@SteveCooper 你可以通过脚本来完成:https://dev59.com/CnVD5IYBdhLWcg3wWKLc#11627 无需关闭SSMS,当你完成初始化/更新后,可以重用ssms,它会记住你最后的连接(虽然它们已经丢失),只需按两次F5,它就会第二次执行。 - DrCopyPaste
@anthoLB29 这是一个老但仍然好用的方法,可以追溯到2013年!我刚刚花了10分钟重置我的密码,只为了给你+1。当我读到它时,我感到有些惊讶,因为它听起来很合理,但又太简单了,好像不可能是真的。但事实证明它确实有效!再见Entity Framework DropCreateDatabaseAlways问题... - AlphaG33k

0

我以前也遇到过同样的问题。结果发现解决方案是在 Visual Studio 的 Server Explorer 选项卡中关闭连接。因此,您可以检查一下在 Server Explorer 中连接是否仍然处于打开状态。


0

我遇到了相同的错误。在我的情况下,我只是关闭了与数据库的连接,然后重新连接,因为在我的情况下添加新模型和脚手架新控制器。但这是一个非常简单的解决方案,并不推荐所有场景中使用,如果想保留数据。


-1

这很简单,因为你仍然在某个地方使用着相同的数据库,或者一个连接仍然是打开状态。

所以首先执行 "USE master"(如果存在,但通常是存在的),然后再删除其他数据库。这总是有效的!

谢谢,John


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接