Search code examples
c#file-ioconnection-stringsql-server-expressconnection-pooling

Can't delete file-only SQL Server Express database after closing connection


Here is a complete program that creates a SQL Server Express database and removes it from the local instance, leaving a file-only database to play with. After playing with it, I want to delete it.

At the start of the Main method, I can delete the db files from previous runs successfully.

However if I try to delete them at the end of the Main method, the operation fails with

The process cannot access the file 'c:\temp.mdf' because it is being used by another process.

According to http://blogs.msdn.com/b/sqlexpress/archive/2008/02/22/sql-express-behaviors-idle-time-resources-usage-auto-close-and-user-instances.aspx, with AUTO_CLOSE on as is default for SQL Server Express, after 300ms of idleness, SQL Server Express should release access to the file, but it appears this is not happening.

Does anyone know how I can get this to work so I can clean up after myself?

TIA

using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace ConsoleApplication3 {
class Program {

    private const string ConnectionStringToFile = @"Data Source=.\SqlExpress;Integrated Security=True;AttachDbFileName={0};User Instance=True";
    private const string ConnectionStringToTempDb = @"Data Source=.\SqlExpress;Initial Catalog=TempDb;Integrated Security=True;User Instance=True;";

    private const string CreateDbSql = "CREATE DATABASE {0} ON PRIMARY (NAME='{0}', FILENAME='{1}');";
    private const string DetachDbSql = "EXEC sp_detach_db '{0}', 'true';";

    private static SqlConnection GetConnection(string connectionString) {
        var conn = new SqlConnection(connectionString);
        Debug.WriteLine("Created", "Connection");
        Debug.Indent();
        conn.StateChange += ConnectionStateChange;
        conn.InfoMessage += ConnectionInfoMessage;
        conn.Disposed += ConnectionDisposed;
        return conn;
    }

    private static void ConnectionDisposed(object sender, EventArgs e) {
        SqlConnection conn = (SqlConnection)sender;
        conn.StateChange -= ConnectionStateChange;
        conn.InfoMessage += ConnectionInfoMessage;
        conn.Disposed += ConnectionDisposed;
        Debug.Unindent();
        Debug.WriteLine("Disposed", "Connection");
    }

    private static void ConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e) {
        Debug.WriteLine("InfoMessage: " + e.Message, "Connection");
    }

    private static void ConnectionStateChange(object sender, System.Data.StateChangeEventArgs e) {
        Debug.WriteLine("StateChange: from " + e.OriginalState + " to " + e.CurrentState, "Connection");
    }

    static void Main() {

        const string DbName = "temp";
        const string DbPath = "c:\\temp.mdf";
        const string DbLogFile = "c:\\temp_log.ldf";

        if (File.Exists(DbPath)) File.Delete(DbPath);
        if (File.Exists(DbLogFile)) File.Delete(DbLogFile);

        using (var conn = GetConnection(ConnectionStringToTempDb)) {
            conn.Open();
            using (var command = conn.CreateCommand()) {
                command.CommandText = string.Format(CreateDbSql, DbName, DbPath);
                command.ExecuteNonQuery();
                command.CommandText = string.Format(DetachDbSql, DbName);
                Debug.WriteLine("Detach result: " + command.ExecuteScalar(), "Database"); 
            }
        }

        using (var conn = GetConnection(string.Format(ConnectionStringToFile, DbPath))) {
            conn.Open();
            using (var command = conn.CreateCommand()) {
                command.CommandText = "PRINT 'Successfully connected to database.'";
                command.ExecuteNonQuery();
                command.CommandText = "CREATE TABLE temp (temp int)";
                command.ExecuteNonQuery();
                command.CommandText = "INSERT temp VALUES (1);";
                command.ExecuteNonQuery();
            }
        }

        // takes 300ms apparently: http://blogs.msdn.com/b/sqlexpress/archive/2008/02/22/sql-express-behaviors-idle-time-resources-usage-auto-close-and-user-instances.aspx
        Thread.Sleep(1000);
        if (File.Exists(DbPath)) File.Delete(DbPath);
        if (File.Exists(DbLogFile)) File.Delete(DbLogFile);
    }
}
}

Solution

  • I found the answer after trawling around the documentation. Just before closing the last connection to the database (or create one for the purpose), clear the connection pool using SqlConnection.ClearPool(SqlConnection connection) as shown below:

    using System;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.IO;
    using System.Threading;
    
    namespace ConsoleApplication3 {
      class Program {
    
        private const string ConnectionStringToFile = @"Data Source=.\SqlExpress;Integrated Security=True;AttachDbFileName={0};User Instance=True";
        private const string ConnectionStringToTempDb = @"Data Source=.\SqlExpress;Initial Catalog=TempDb;Integrated Security=True;User Instance=True;";
    
        private const string CreateDbSql = "CREATE DATABASE {0} ON PRIMARY (NAME='{0}', FILENAME='{1}');";
        private const string DetachDbSql = "EXEC sp_detach_db '{0}', 'true';";
    
        private static SqlConnection GetConnection(string connectionString) {
          var conn = new SqlConnection(connectionString);
          Debug.WriteLine("Created", "Connection");
          Debug.Indent();
          conn.StateChange += ConnectionStateChange;
          conn.InfoMessage += ConnectionInfoMessage;
          conn.Disposed += ConnectionDisposed;
          return conn;
        }
    
        private static void ConnectionDisposed(object sender, EventArgs e) {
          SqlConnection conn = (SqlConnection)sender;
          conn.StateChange -= ConnectionStateChange;
          conn.InfoMessage += ConnectionInfoMessage;
          conn.Disposed += ConnectionDisposed;
          Debug.Unindent();
          Debug.WriteLine("Disposed", "Connection");
        }
    
        private static void ConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e) {  
          Debug.WriteLine("InfoMessage: " + e.Message, "Connection");
        }
    
        private static void ConnectionStateChange(object sender, System.Data.StateChangeEventArgs e) {
          Debug.WriteLine("StateChange: from " + e.OriginalState + " to " + e.CurrentState, "Connection");
        }
    
        static void Main() {
    
          const string DbName = "temp";
          const string DbPath = "c:\\temp.mdf";
          const string DbLogFile = "c:\\temp_log.ldf";
    
          using (var conn = GetConnection(ConnectionStringToTempDb)) {
            conn.Open();
            using (var command = conn.CreateCommand()) {
              command.CommandText = string.Format(CreateDbSql, DbName, DbPath);
              command.ExecuteNonQuery();
              command.CommandText = string.Format(DetachDbSql, DbName);
              Debug.WriteLine("Detach result: " + command.ExecuteScalar(), "Database"); 
            }
          }
    
          using (var conn = GetConnection(string.Format(ConnectionStringToFile, DbPath))) {
            conn.Open();
            using (var command = conn.CreateCommand()) {
              command.CommandText = "PRINT 'Successfully connected to database.'";
              command.ExecuteNonQuery();
              command.CommandText = "CREATE TABLE temp (temp int)";
              command.ExecuteNonQuery();
              command.CommandText = "INSERT temp VALUES (1);";
              command.ExecuteNonQuery();
            }
            SqlConnection.ClearPool(conn);
          }
    
          // SqlExpress takes 300ms to go idle:
          // http://blogs.msdn.com/b/sqlexpress/archive/2008/02/22/sql-express-behaviors-idle-time-resources-usage-auto-close-and-user-instances.aspx
          Thread.Sleep(500); // wait for 500ms just in case (seems to work with 300 though).
          if (File.Exists(DbPath)) File.Delete(DbPath);
          if (File.Exists(DbLogFile)) File.Delete(DbLogFile);
        }
      }
    }