Skip to content

Instantly share code, notes, and snippets.

@drub0y
Last active December 16, 2015 15:29
Show Gist options
  • Save drub0y/5456264 to your computer and use it in GitHub Desktop.
Save drub0y/5456264 to your computer and use it in GitHub Desktop.
An utility class designed to work with SQL LocalDb instances inside of integration tests.
using System;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace HackedBraind.Testing.Utilities
{
internal class LocalDbPrivateInstance : IDisposable
{
#region Fields
private static IntPtr LocalDbDllHandle;
private static string LocalDbVersion;
private static LocalDbCreateInstanceFunction LocalDbCreateInstance;
private static LocalDbDeleteInstanceFunction LocalDbDeleteInstance;
private static LocalDbStopInstanceFunction LocalDbStopInstance;
private static LocalDbFormatMessageFunction LocalDbFormatMessage;
private static LocalDbGetInstanceInfoFunction LocalDbGetInstanceInfo;
private readonly string instanceName;
#endregion
#region Constructor/Finalizer
static LocalDbPrivateInstance()
{
LocalDbPrivateInstance.LoadSQLUserInstanceLibrary();
LocalDbPrivateInstance.BindSQLUserInstancePInvokeMethods();
}
~LocalDbPrivateInstance()
{
this.Dispose(false);
}
private LocalDbPrivateInstance(string instanceName)
{
this.instanceName = instanceName;
}
#endregion
#region Type specific methods
public static LocalDbPrivateInstance CreateInstance(string instanceName)
{
return LocalDbPrivateInstance.CreateInstance(instanceName, true);
}
public static LocalDbPrivateInstance CreateInstance(string instanceName, bool deleteIfAlreadyExists)
{
if(deleteIfAlreadyExists)
{
LocalDbPrivateInstance.DeleteIfAlreadyExists(instanceName);
}
int createInstanceResult = LocalDbPrivateInstance.LocalDbCreateInstance(LocalDbPrivateInstance.LocalDbVersion, instanceName, 0);
if(createInstanceResult != 0)
{
throw new Exception("An error occurred creating the local DB intance: " + LocalDbPrivateInstance.FormatMessage(createInstanceResult));
}
return new LocalDbPrivateInstance(instanceName);
}
public SqlConnection CreateConnection(string connectionString)
{
SqlConnectionStringBuilder sqlConnectionStringBuilder = new SqlConnectionStringBuilder(connectionString);
sqlConnectionStringBuilder.DataSource = "(LocalDb)\\" + this.instanceName;
return new SqlConnection(sqlConnectionStringBuilder.ToString());
}
public void AttachDatabases(IEnumerable<KeyValuePair<string, string>> namesAndPaths)
{
using(SqlConnection connection = this.CreateConnection("Database=master;MultipleActiveResultSets=true;"))
{
connection.Open();
foreach(KeyValuePair<string, string> nameAndPath in namesAndPaths)
{
LocalDbPrivateInstance.AttachDatabase(connection, nameAndPath.Key, nameAndPath.Value);
}
}
}
public void AttachDatabase(string name, string path)
{
using(SqlConnection connection = this.CreateConnection("Database=master;MultipleActiveResultSets=true;"))
{
connection.Open();
LocalDbPrivateInstance.AttachDatabase(connection, name, path);
}
}
public void DetatchAllDatabases()
{
using(SqlConnection connection = this.CreateConnection("Database=master;MultipleActiveResultSets=true;"))
{
connection.Open();
SqlCommand getDatabasesCommand = connection.CreateCommand();
getDatabasesCommand.CommandText = "SELECT name FROM sys.databases WHERE owner_sid <> 0x01";
SqlCommand detatchDatabaseCommand = connection.CreateCommand();
detatchDatabaseCommand.CommandText = "EXEC master.dbo.sp_detach_db @dbname = @DatabaseName, @keepfulltextindexfile=N'false'";
SqlParameter detatchDatabaseNameParameter = detatchDatabaseCommand.Parameters.Add("@DatabaseName", System.Data.SqlDbType.NVarChar);
using(SqlDataReader databaseReader = getDatabasesCommand.ExecuteReader())
{
while(databaseReader.Read())
{
detatchDatabaseNameParameter.Value = databaseReader[0];
detatchDatabaseCommand.ExecuteNonQuery();
}
}
}
}
#endregion
#region IDisposable implementation
public void Dispose()
{
this.Dispose(true);
}
#endregion
#region Helper methods
private void Dispose(bool disposing)
{
if(disposing)
{
GC.SuppressFinalize(this);
}
int stopInstanceResult = LocalDbPrivateInstance.LocalDbStopInstance(this.instanceName, 0);
if(stopInstanceResult != 0)
{
throw new Exception("An error occurred stopping the local DB intance: " + LocalDbPrivateInstance.FormatMessage(stopInstanceResult));
}
int deleteInstanceResult = LocalDbPrivateInstance.LocalDbDeleteInstance(this.instanceName, 0);
if(stopInstanceResult != 0)
{
throw new Exception("An error occurred deleting the local DB intance: " + LocalDbPrivateInstance.FormatMessage(deleteInstanceResult));
}
}
private static string FormatMessage(int hresult)
{
int messageBufferLength = 1024;
StringBuilder stringBuilder = new StringBuilder(messageBufferLength);
int formatMessageResult = LocalDbPrivateInstance.LocalDbFormatMessage(hresult, 0, 1033, stringBuilder, ref messageBufferLength);
if(formatMessageResult != 0)
{
throw new Exception(string.Format("An error occurred formatting the message for result {0}: {1}", hresult, formatMessageResult));
}
return stringBuilder.ToString();
}
private static void LoadSQLUserInstanceLibrary()
{
RegistryKey localDbInstalledVersionsRegistryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Microsoft SQL Server Local DB\Installed Versions");
string latestVersionSubKeyName = localDbInstalledVersionsRegistryKey.GetSubKeyNames().Last();
RegistryKey latestVersionRegistryKey = localDbInstalledVersionsRegistryKey.OpenSubKey(latestVersionSubKeyName);
string latestVersionDllPath = (string)latestVersionRegistryKey.GetValue("InstanceApiPath");
IntPtr latestVersionDllHandle = LocalDbPrivateInstance.LoadLibrary(latestVersionDllPath);
if(latestVersionDllHandle == IntPtr.Zero)
{
throw new Exception(string.Format("LoadLibrary of \"{0}\" failed: {1}", latestVersionDllPath, Marshal.GetLastWin32Error()));
}
LocalDbPrivateInstance.LocalDbVersion = latestVersionSubKeyName;
LocalDbPrivateInstance.LocalDbDllHandle = latestVersionDllHandle;
}
private static void AttachDatabase(SqlConnection connection, string name, string path)
{
using(SqlCommand command = connection.CreateCommand())
{
command.CommandText = string.Format("CREATE DATABASE {0} ON (FILENAME = '{1}') FOR ATTACH", name, path);
command.ExecuteNonQuery();
}
}
private static void BindSQLUserInstancePInvokeMethods()
{
LocalDbPrivateInstance.LocalDbCreateInstance = (LocalDbCreateInstanceFunction)Marshal.GetDelegateForFunctionPointer(LocalDbPrivateInstance.GetProcAddress(LocalDbPrivateInstance.LocalDbDllHandle, "LocalDBCreateInstance"), typeof(LocalDbCreateInstanceFunction));
LocalDbPrivateInstance.LocalDbDeleteInstance = (LocalDbDeleteInstanceFunction)Marshal.GetDelegateForFunctionPointer(LocalDbPrivateInstance.GetProcAddress(LocalDbPrivateInstance.LocalDbDllHandle, "LocalDBDeleteInstance"), typeof(LocalDbDeleteInstanceFunction));
LocalDbPrivateInstance.LocalDbStopInstance = (LocalDbStopInstanceFunction)Marshal.GetDelegateForFunctionPointer(LocalDbPrivateInstance.GetProcAddress(LocalDbPrivateInstance.LocalDbDllHandle, "LocalDBStopInstance"), typeof(LocalDbStopInstanceFunction));
LocalDbPrivateInstance.LocalDbFormatMessage = (LocalDbFormatMessageFunction)Marshal.GetDelegateForFunctionPointer(LocalDbPrivateInstance.GetProcAddress(LocalDbPrivateInstance.LocalDbDllHandle, "LocalDBFormatMessage"), typeof(LocalDbFormatMessageFunction));
LocalDbPrivateInstance.LocalDbGetInstanceInfo = (LocalDbGetInstanceInfoFunction)Marshal.GetDelegateForFunctionPointer(LocalDbPrivateInstance.GetProcAddress(LocalDbPrivateInstance.LocalDbDllHandle, "LocalDBGetInstanceInfo"), typeof(LocalDbGetInstanceInfoFunction));
}
private static void DeleteIfAlreadyExists(string instanceName)
{
int stopInstanceResult = LocalDbPrivateInstance.LocalDbStopInstance(instanceName, 1);
if(stopInstanceResult == 0
||
stopInstanceResult == -1983577849)
{
int deleteInstanceResult = LocalDbPrivateInstance.LocalDbDeleteInstance(instanceName, 0);
if(deleteInstanceResult != 0
&&
deleteInstanceResult != -1983577849)
{
throw new Exception("An error occurred attempting to delete an already existing instance: " + LocalDbPrivateInstance.FormatMessage(deleteInstanceResult));
}
}
else
{
throw new Exception("An error occurred attempting to stop an already existing instance: " + LocalDbPrivateInstance.FormatMessage(stopInstanceResult));
}
}
#endregion
#region P/Invoke imports
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
private delegate int LocalDbCreateInstanceFunction(string version, string instanceName, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
private delegate int LocalDbDeleteInstanceFunction(string instanceName, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
private delegate int LocalDbStopInstanceFunction(string instanceName, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto, SetLastError = true)]
private delegate int LocalDbFormatMessageFunction(int hresult, int flags, int languageId, StringBuilder messageBuffer, ref int messageBufferLength);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);
#endregion
}
internal static class TypeExtensions
{
public static void AttachDeployedDatabases(this LocalDbPrivateInstance dbInstance, Type testType)
{
IEnumerable<KeyValuePair<string, string>> deployedMdfDetails = from dia in testType.GetCustomAttributes(typeof(DeploymentItemAttribute), true).Cast<DeploymentItemAttribute>()
where dia.Path.EndsWith(".mdf")
let fileName = Path.GetFileName(dia.Path)
select new KeyValuePair<string, string>(Path.GetFileNameWithoutExtension(fileName), Path.GetFullPath(Path.Combine(dia.OutputDirectory, fileName)));
dbInstance.AttachDatabases(deployedMdfDetails);
}
}
}
@nshenoy
Copy link

nshenoy commented Dec 18, 2013

_Detatch_AllDatabases()" should be DetachAllDatabases()" (no t)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment