You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SqlServerTestStore.cs 4.8 KiB

8 years ago
8 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. using System;
  2. using System.Data.Common;
  3. using System.Data.SqlClient;
  4. using System.IO;
  5. using System.Threading;
  6. namespace Cap.Consistency.EntityFrameworkCore.Test
  7. {
  8. public class SqlServerTestStore : IDisposable
  9. {
  10. public const int CommandTimeout = 90;
  11. public static string CreateConnectionString(string name) {
  12. var connStrBuilder = new SqlConnectionStringBuilder(TestEnvironment.Config["Test:SqlServer:DefaultConnectionString"]) {
  13. InitialCatalog = name
  14. };
  15. return connStrBuilder.ConnectionString;
  16. }
  17. public static SqlServerTestStore CreateScratch(bool createDatabase = true)
  18. => new SqlServerTestStore(GetScratchDbName()).CreateTransient(createDatabase);
  19. private SqlConnection _connection;
  20. private readonly string _name;
  21. private bool _deleteDatabase;
  22. private SqlServerTestStore(string name) {
  23. _name = name;
  24. }
  25. private static string GetScratchDbName() {
  26. string name;
  27. do {
  28. name = "Scratch_" + Guid.NewGuid();
  29. } while (DatabaseExists(name)
  30. || DatabaseFilesExist(name));
  31. return name;
  32. }
  33. private static void WaitForExists(SqlConnection connection) {
  34. var retryCount = 0;
  35. while (true) {
  36. try {
  37. connection.Open();
  38. connection.Close();
  39. return;
  40. }
  41. catch (SqlException e) {
  42. if (++retryCount >= 30
  43. || (e.Number != 233 && e.Number != -2 && e.Number != 4060)) {
  44. throw;
  45. }
  46. SqlConnection.ClearPool(connection);
  47. Thread.Sleep(100);
  48. }
  49. }
  50. }
  51. private SqlServerTestStore CreateTransient(bool createDatabase) {
  52. _connection = new SqlConnection(CreateConnectionString(_name));
  53. if (createDatabase) {
  54. using (var master = new SqlConnection(CreateConnectionString("master"))) {
  55. master.Open();
  56. using (var command = master.CreateCommand()) {
  57. command.CommandTimeout = CommandTimeout;
  58. command.CommandText = $"{Environment.NewLine}CREATE DATABASE [{_name}]";
  59. command.ExecuteNonQuery();
  60. WaitForExists(_connection);
  61. }
  62. }
  63. _connection.Open();
  64. }
  65. _deleteDatabase = true;
  66. return this;
  67. }
  68. private static bool DatabaseExists(string name) {
  69. using (var master = new SqlConnection(CreateConnectionString("master"))) {
  70. master.Open();
  71. using (var command = master.CreateCommand()) {
  72. command.CommandTimeout = CommandTimeout;
  73. command.CommandText = $@"SELECT COUNT(*) FROM sys.databases WHERE name = N'{name}'";
  74. return (int)command.ExecuteScalar() > 0;
  75. }
  76. }
  77. }
  78. private static bool DatabaseFilesExist(string name) {
  79. var userFolder = Environment.GetEnvironmentVariable("USERPROFILE") ??
  80. Environment.GetEnvironmentVariable("HOME");
  81. return userFolder != null
  82. && (File.Exists(Path.Combine(userFolder, name + ".mdf"))
  83. || File.Exists(Path.Combine(userFolder, name + "_log.ldf")));
  84. }
  85. private void DeleteDatabase(string name) {
  86. using (var master = new SqlConnection(CreateConnectionString("master"))) {
  87. master.Open();
  88. using (var command = master.CreateCommand()) {
  89. command.CommandTimeout = CommandTimeout;
  90. // Query will take a few seconds if (and only if) there are active connections
  91. // SET SINGLE_USER will close any open connections that would prevent the drop
  92. command.CommandText
  93. = string.Format(@"IF EXISTS (SELECT * FROM sys.databases WHERE name = N'{0}')
  94. BEGIN
  95. ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
  96. DROP DATABASE [{0}];
  97. END", name);
  98. command.ExecuteNonQuery();
  99. }
  100. }
  101. }
  102. public DbConnection Connection => _connection;
  103. public void Dispose() {
  104. _connection.Dispose();
  105. if (_deleteDatabase) {
  106. DeleteDatabase(_name);
  107. }
  108. }
  109. }
  110. }