diff --git a/pages/raw-usage.md b/pages/raw-usage.md index 90dae8f0..b5bb6ec3 100644 --- a/pages/raw-usage.md +++ b/pages/raw-usage.md @@ -271,7 +271,7 @@ public async Task SharedDatabase() AreEqual(0, data.Count); } ``` -snippet source | anchor +snippet source | anchor Pass `useTransaction: true` to get an auto-rolling-back transaction, allowing writes without affecting other tests. @@ -307,5 +307,5 @@ public async Task SharedDatabase_WithTransaction() AreEqual(0, data.Count); } ``` -snippet source | anchor +snippet source | anchor diff --git a/pages/template-database-size.md b/pages/template-database-size.md index bc73856e..eb2534b9 100644 --- a/pages/template-database-size.md +++ b/pages/template-database-size.md @@ -21,7 +21,7 @@ To have a smaller file size [DBCC SHRINKFILE](https://docs.microsoft.com/en-us/s use model; dbcc shrinkfile(modeldev, {size}) ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8fc2ee3a..8a97ed8d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CA1416;CS8632;NU1608;NU1109 - 24.1.0 + 24.1.1 preview 1.0.0 false diff --git a/src/LocalDb.Tests/Tests.cs b/src/LocalDb.Tests/Tests.cs index db5644cd..774b7229 100644 --- a/src/LocalDb.Tests/Tests.cs +++ b/src/LocalDb.Tests/Tests.cs @@ -78,6 +78,40 @@ public async Task Callback() True(callbackCalled); } + [Test] + public async Task BuildTemplateLeavesConnectionOpen() + { + // Mimics a buildTemplate callback (e.g. an SMO ServerConnection) that opens its + // own connection to the template and leaves it open. Setting read_committed_snapshot + // requires exclusive access, so the rebuild must evict that lingering session + // ("with rollback immediate"); otherwise the alter blocks until the command timeout. + SqlConnection? leaked = null; + using var instance = new SqlInstance( + "Tests_BuildTemplateLeavesConnectionOpen", + async connection => + { + await TestDbBuilder.CreateTable(connection); + leaked = new(connection.ConnectionString); + await leaked.OpenAsync(); + }); + + try + { + await using var database = await instance.Build(); + var data = await TestDbBuilder.AddData(database.Connection); + Contains(data, await TestDbBuilder.GetData(database.Connection)); + } + finally + { + if (leaked != null) + { + await leaked.DisposeAsync(); + } + + instance.Cleanup(); + } + } + //[Test] //public async Task SuppliedTemplate() //{ diff --git a/src/LocalDb/SqlBuilder.cs b/src/LocalDb/SqlBuilder.cs index 4b78b40c..041fdd8a 100644 --- a/src/LocalDb/SqlBuilder.cs +++ b/src/LocalDb/SqlBuilder.cs @@ -58,10 +58,14 @@ create database [template] on // shared locks, preventing S/X-lock deadlocks // between parallel [SharedDbWithTransaction] tests // against the same shared database. + // read_committed_snapshot requires exclusive access to the database, so it uses + // "with rollback immediate" to evict any sessions a buildTemplate/callback left + // open (e.g. an SMO ServerConnection). Without it the statement blocks on those + // sessions until the command timeout expires. public static string TemplateSettingsCommand = """ alter database [template] set auto_update_statistics off; - alter database [template] set read_committed_snapshot on; + alter database [template] set read_committed_snapshot on with rollback immediate; """; public static string DetachAndShrinkTemplateCommand =