views:

916

answers:

4

When using SQL Server Express 2005's User Instance feature with a connection string like this:

<add name="Default" connectionString="Data Source=.\SQLExpress;
  AttachDbFilename=C:\My App\Data\MyApp.mdf;
  Initial Catalog=MyApp;
  User Instance=True;
  MultipleActiveResultSets=true;
  Trusted_Connection=Yes;" />

We find that we can't copy the database files MyApp.mdf and MyApp_Log.ldf (because they're locked) even after stopping the SqlExpress service, and have to resort to setting the SqlExpress service from automatic to manual startup mode, and then restarting the machine, before we can then copy the files.

It was my understanding that stopping the SqlExpress service should stop all the user instances as well, which should release the locks on those files. But this does not seem to be the case - could anyone shed some light on how to stop a user instance, such that it's database files are no longer locked?


Update

OK, I stopped being lazy and fired up Process Explorer. Lock was held by sqlserver.exe - but there are two instances of sql server:

sqlserver.exe  PID: 4680  User Name: DefaultAppPool
sqlserver.exe  PID: 4644  User Name: NETWORK SERVICE

The file is open by the sqlserver.exe instance with the PID: 4680

Stopping the "SQL Server (SQLEXPRESS)" service, killed off the process with PID: 4644, but left PID: 4680 alone.

Seeing as the owner of the remaining process was DefaultAppPool, next thing I tried was stopping IIS (this database is being used from an ASP.Net application). Unfortunately this didn't kill the process off either.

Manually killing off the remaining sql server process does remove the open file handle on the database files, allowing them to be copied/moved.

Unfortunately I wish to copy/restore those files in some pre/post install tasks of a WiX installer - as such I was hoping there might be a way to achieve this by stopping a windows service, rather then having to shell out to kill all instances of sqlserver.exe as that poses some problems:

  1. Killing all the sqlserver.exe instances may have undesirable consequencies for users with other Sql Server instances on their machines.
  2. I can't restart those instances easily.
  3. Introduces additional complexities into the installer.

Does anyone have any further thoughts on how to shutdown instances of sql server associated with a specific user instance?

+1  A: 

I have been using the following helper method to detach MDF files attached to SQL Server in unit tests (so that SQ Server releases locks on MDF and LDF files and the unit test can clean up after itself)...

private static void DetachDatabase(DbProviderFactory dbProviderFactory, string connectionString)
{
    using (var connection = dbProviderFactory.CreateConnection())
    {
        if (connection is SqlConnection)
        {
            SqlConnection.ClearAllPools();

            // convert the connection string (to connect to 'master' db), extract original database name
            var sb = dbProviderFactory.CreateConnectionStringBuilder();
            sb.ConnectionString = connectionString;
            sb.Remove("AttachDBFilename");
            var databaseName = sb["database"].ToString();
            sb["database"] = "master";
            connectionString = sb.ToString();

            // detach the original database now
            connection.ConnectionString = connectionString;
            connection.Open();
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "sp_detach_db";
                cmd.CommandType = CommandType.StoredProcedure;

                var p = cmd.CreateParameter();
                p.ParameterName = "@dbname";
                p.DbType = DbType.String;
                p.Value = databaseName;
                cmd.Parameters.Add(p);

                p = cmd.CreateParameter();
                p.ParameterName = "@skipchecks";
                p.DbType = DbType.String;
                p.Value = "true";
                cmd.Parameters.Add(p);

                p = cmd.CreateParameter();
                p.ParameterName = "@keepfulltextindexfile";
                p.DbType = DbType.String;
                p.Value = "false";
                cmd.Parameters.Add(p);

                cmd.ExecuteNonQuery();
            }
        }
    }
}

Notes:

  • SqlConnection.ClearAllPools() was very helpful in eliminating "stealth" connections (when a connection is pooled, it will stay active even though you 'Close()' it; by explicitely clearing pool connections you don't have to worry about setting pooling flag to false in all connection strings).
  • The "magic ingredient" is call to the system stored procedure sp_detach_db (Transact-SQL).
  • My connection strings included "AttachDBFilename" but didn't include "User Instance=True", so this solution might not apply to your scenario
Milan Gardian
Thanks for the code - In my case I need to invoke this from a WiX installer (rather then the application itself, test fixture etc.) - also unfortunately the db user for the user instance may not have enough permissions to invoke sp_detach_db on master. But It does give me some other options to try out.
Bittercoder
The following MSDN documentation page http://msdn.microsoft.com/en-us/library/bb264564(SQL.90).aspx states: "Detaching the database from the instance by calling sp_detach_db will close the file. This is the method Visual Studio uses to ensure that the database file is closed when the IDE switches between user instances." -- it seems Visual Studio itself uses sp_detach_db to close user instances :-). I assume the SSEUtil tool suggested by @AMissico uses it too (and it comes in handy exe form invokable from Wix)
Milan Gardian
I updated my answer to include the actual command used by SSEUtil.
AMissico
I think @keepfulltextindexfile is being deprecated following the changes in Sql 2008
Keith
+5  A: 

Use "SQL Server Express Utility" (SSEUtil.exe) or the command to detach the database used by SSEUtil.

SQL Server Express Utility, SSEUtil is a tool that lets you easily interact with SQL Server, http://www.microsoft.com/downloads/details.aspx?FamilyID=fa87e828-173f-472e-a85c-27ed01cf6b02&amp;DisplayLang=en

Also, the default timeout to stop the service after the last connection is closed is one hour. On your development box, you may want to change this to five minutes (the minimum allowed).

In addition, you may have an open connection through Visual Studio's Server Explorer Data Connections, so be sure to disconnect from any database there.

H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb
5. C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIENTS\CORE.DATA.C
LIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF

H:\Tools\SQL Server Express Utility>sseutil -d C:\DEV*
Failed to detach 'C:\DEV_\APP\VISUAL STUDIO 2008\PROJECTS\MISSICO.LIBRARY.1\CLIE
NTS\CORE.DATA.CLIENT\BIN\DEBUG\CORE.DATA.CLIENT.MDF'

H:\Tools\SQL Server Express Utility>sseutil -l
1. master
2. tempdb
3. model
4. msdb

H:\Tools\SQL Server Express Utility>

Using .NET Refector the following command is used to detach the database.

string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);
AMissico
Thanks, I need to invoke this action from within a WiX installer on many customers machines (who won't have SSEUtil installed) but I'll explore this as possible option.
Bittercoder
SSEUtil is .NET Application. Using .NET Reflector to see how it is detaching database.
AMissico
I suggest making the requirement of WiX installer as part of your full question. (Your last sentence.) It is easy to miss this.
AMissico
A: 

This may not be what you are looking for, but the free tool Unlocker has a command line interface that could be run from WIX. (I have used unlocker for a while and have found it stable and very good at what it does best, unlocking files.)

Unlocker can unlock and move/delete most any file.

The downside to this is the apps that need a lock on the file will no longer have it. (But sometimes still work just fine.) Note that this does not kill the process that has the lock. It just removes it's lock. (It may be that restarting the sql services that you are stopping will be enough for it to re-lock and/or work correctly.)

You can get Unlocker from here: http://ccollomb.free.fr/unlocker/

To see the command line options run unlocker -H Here they are for convenience:

Unlocker 1.8.8

Command line usage:
   Unlocker.exe Object [Option]
Object:
   Complete path including drive to a file or folder
Options:
   /H or -H or /? or -?: Display command line usage 
   /S or -S: Unlock object without showing the GUI 
   /L or -L: Object is a text file containing the list of files to unlock 
   /LU or -LU: Similar to /L with a unicode list of files to unlock 
   /O or -O: Outputs Unlocker-Log.txt log file in Unlocker directory 
   /D or -D: Delete file 
   /R Object2 or -R Object2: Rename file, if /L or /LU is set object2 points to a text file containing the new name of files 
   /M Object2 or -M Object2: Move file, if /L or /LU is set object2 points a text file containing the new location of files 

Assuming your goal was to replace C:\My App\Data\MyApp.mdf with a file from your installer, you would want something like unlocker C:\My App\Data\MyApp.mdf -S -D. This would delete the file so you could copy in a new one.

Vaccano
Hmmmm, gotta love it when you get a down vote with no comment why.
Vaccano
I would imagine the down vote was because if the mdf is locked, it is for a reason, and using unlocker may corrupt the database or something worst.
AMissico
+1  A: 

I can't comment yet because I don't have high enough rep yet. Can someone move this info to the other answer so we don't have a dupe?

I just used this post to solve my WIX uninstall problem. I used this line from AMissico's answer.

string.Format("USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{1}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tEXEC sp_detach_db [{1}]\nEND", dbName, str);

Worked pretty well when using WIX, only I had to add one thing to make it work for me.

I had took out the sp_detach_db and then brought the db back online. If you don't, WIX will leave the mdf files around after the uninstall. Once I brought the db back online WIX would properly delete the mdf files.

Here is my modified line.

string.Format( "USE master\nIF EXISTS (SELECT * FROM sysdatabases WHERE name = N'{0}')\nBEGIN\n\tALTER DATABASE [{0}] SET OFFLINE WITH ROLLBACK IMMEDIATE\n\tALTER DATABASE [{0}] SET ONLINE\nEND", dbName );
KnightsArmy