Last active
December 16, 2015 15:29
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
_Detatch_AllDatabases()" should be DetachAllDatabases()" (no
t)