diff --git a/pages/design.md b/pages/design.md
index 2259c99b..a75da739 100644
--- a/pages/design.md
+++ b/pages/design.md
@@ -44,6 +44,17 @@ The usual approach for consuming the API in a test project is as follows.
This assumes that there is a schema and data (and DbContext in the EntityFramework context) used for all tests. If those caveats are not correct then multiple SqlInstances can be used.
+
+## Template database settings
+
+When the template is built, a few database-level settings are applied. These persist in the template's `.mdf`/`.ldf` files and are inherited by every database attached from the template.
+
+ * `auto_update_statistics off` — avoids background statistics updates causing nondeterministic test timing.
+ * `read_committed_snapshot on` — `READ COMMITTED` uses row versioning instead of shared locks. Required to prevent S/X-lock deadlocks between parallel `[SharedDbWithTransaction]` tests against the same shared database.
+
+If the template files already exist on disk from a previous build, these settings are not reapplied — the template needs to be regenerated (delete the template files, or change the timestamp passed to `SqlInstance`) for new settings to take effect.
+
+
More information:
* [Raw SqlConnection Usage](/pages/raw-usage.md)
diff --git a/pages/ef-usage.md b/pages/ef-usage.md
index 149b8f75..c59a1167 100644
--- a/pages/ef-usage.md
+++ b/pages/ef-usage.md
@@ -292,7 +292,7 @@ public async Task SharedDatabase()
AreEqual(0, count);
}
```
-snippet source | anchor
+snippet source | anchor
Pass `useTransaction: true` to get an auto-rolling-back transaction, allowing writes without affecting other tests.
@@ -318,7 +318,7 @@ public async Task SharedDatabase_WithTransaction()
AreEqual(0, count);
}
```
-snippet source | anchor
+snippet source | anchor
diff --git a/pages/mdsource/design.source.md b/pages/mdsource/design.source.md
index 82888351..356d9216 100644
--- a/pages/mdsource/design.source.md
+++ b/pages/mdsource/design.source.md
@@ -37,6 +37,17 @@ The usual approach for consuming the API in a test project is as follows.
This assumes that there is a schema and data (and DbContext in the EntityFramework context) used for all tests. If those caveats are not correct then multiple SqlInstances can be used.
+
+## Template database settings
+
+When the template is built, a few database-level settings are applied. These persist in the template's `.mdf`/`.ldf` files and are inherited by every database attached from the template.
+
+ * `auto_update_statistics off` — avoids background statistics updates causing nondeterministic test timing.
+ * `read_committed_snapshot on` — `READ COMMITTED` uses row versioning instead of shared locks. Required to prevent S/X-lock deadlocks between parallel `[SharedDbWithTransaction]` tests against the same shared database.
+
+If the template files already exist on disk from a previous build, these settings are not reapplied — the template needs to be regenerated (delete the template files, or change the timestamp passed to `SqlInstance`) for new settings to take effect.
+
+
More information:
* [Raw SqlConnection Usage](/pages/raw-usage.md)
diff --git a/pages/template-database-size.md b/pages/template-database-size.md
index a0dc795d..bc73856e 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/EfLocalDb.Tests/Tests.cs b/src/EfLocalDb.Tests/Tests.cs
index 59e23dc0..3e17ba3a 100644
--- a/src/EfLocalDb.Tests/Tests.cs
+++ b/src/EfLocalDb.Tests/Tests.cs
@@ -78,6 +78,19 @@ await ThrowsTask(() => database.SaveChangesAsync())
.IgnoreStackTrace();
}
+ [Test]
+ public async Task TemplateHasReadCommittedSnapshot()
+ {
+ // Guards against accidental removal of the ALTER DATABASE statement that enables
+ // READ_COMMITTED_SNAPSHOT on the template — required to avoid S/X-lock deadlocks
+ // between parallel [SharedDbWithTransaction] tests on the same shared database.
+ await using var database = await instance.Build();
+ await using var command = database.Connection.CreateCommand();
+ command.CommandText = "select is_read_committed_snapshot_on from sys.databases where name = db_name()";
+ var enabled = (bool) (await command.ExecuteScalarAsync())!;
+ True(enabled);
+ }
+
[Test]
public async Task RemoveData()
{
diff --git a/src/LocalDb/SqlBuilder.cs b/src/LocalDb/SqlBuilder.cs
index da7d3169..4b78b40c 100644
--- a/src/LocalDb/SqlBuilder.cs
+++ b/src/LocalDb/SqlBuilder.cs
@@ -50,6 +50,20 @@ create database [template] on
execute sp_detach_db N'template', 'true';
""";
+ // Database-level settings applied to the template before detach. Persisted in the
+ // .mdf/.ldf files, so every database attached from the template inherits them.
+ // auto_update_statistics off - avoids background stats updates causing
+ // nondeterministic test timing.
+ // read_committed_snapshot on - READ COMMITTED uses row versioning instead of
+ // shared locks, preventing S/X-lock deadlocks
+ // between parallel [SharedDbWithTransaction] tests
+ // against the same shared database.
+ public static string TemplateSettingsCommand =
+ """
+ alter database [template] set auto_update_statistics off;
+ alter database [template] set read_committed_snapshot on;
+ """;
+
public static string DetachAndShrinkTemplateCommand =
"""
use [template];
diff --git a/src/LocalDb/SqlExtensions.cs b/src/LocalDb/SqlExtensions.cs
index b4742f97..b9b08be7 100644
--- a/src/LocalDb/SqlExtensions.cs
+++ b/src/LocalDb/SqlExtensions.cs
@@ -1,8 +1,8 @@
static class SqlExtensions
{
- public static async Task ExecuteCommandAsync(this SqlConnection connection, string commandText)
+ public static async Task ExecuteCommandAsync(this SqlConnection connection, params string[] commandTexts)
{
- commandText = commandText.Trim();
+ var commandText = string.Join('\n', commandTexts).Trim();
try
{
diff --git a/src/LocalDb/Wrapper.cs b/src/LocalDb/Wrapper.cs
index 2b58e3f5..764f73e7 100644
--- a/src/LocalDb/Wrapper.cs
+++ b/src/LocalDb/Wrapper.cs
@@ -260,10 +260,10 @@ async Task CreateAndDetachTemplate(
await connection.ExecuteCommandAsync("checkpoint");
}
- await masterConnection.ExecuteCommandAsync("alter database [template] set auto_update_statistics off");
-
- // Detach the template database after callback completes
- await masterConnection.ExecuteCommandAsync(SqlBuilder.DetachTemplateCommand);
+ // Apply template settings then detach in a single batch.
+ await masterConnection.ExecuteCommandAsync(
+ SqlBuilder.TemplateSettingsCommand,
+ SqlBuilder.DetachTemplateCommand);
}
}
}
@@ -299,8 +299,9 @@ async Task Rebuild(DateTime timestamp, Func buildTemplate,
await connection.ExecuteCommandAsync("checkpoint");
}
- await masterConnection.ExecuteCommandAsync("alter database [template] set auto_update_statistics off");
- await masterConnection.ExecuteCommandAsync(SqlBuilder.DetachAndShrinkTemplateCommand);
+ await masterConnection.ExecuteCommandAsync(
+ SqlBuilder.TemplateSettingsCommand,
+ SqlBuilder.DetachAndShrinkTemplateCommand);
File.SetCreationTime(DataFile, timestamp);
}