Skip to content

Commit 09c7fca

Browse files
committed
Mitigate EF Core internal race condition on first-time model materialization with [ClassInitialize] warm-up.
1 parent a503020 commit 09c7fca

26 files changed

Lines changed: 96 additions & 0 deletions

SqlServerSimulator.Tests.EFCore/AssemblyHooks.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,21 @@ public static async Task HotPath(TestContext context)
1919
using var dbContext = new TestDbContext(1, 2);
2020
_ = await dbContext.Rows.Select(x => x.Id).FirstOrDefaultAsync(context.CancellationToken);
2121
}
22+
23+
/// <summary>
24+
/// Drives one EF Core <see cref="DbContext"/> subtype's model build +
25+
/// runtime-initializer to completion on a single thread, so subsequent
26+
/// parallel test methods that share the type don't race on EF Core's
27+
/// internal model cache. <c>GetRelationalModel</c> is the call that
28+
/// raises "model must be finalized and its runtime dependencies must be
29+
/// initialized" when another thread observes a half-initialized cached
30+
/// model; invoking it here guarantees the cache entry is fully wired
31+
/// before parallel use begins. Call from each test class's
32+
/// <c>[ClassInitialize]</c> when the class declares its own context type.
33+
/// </summary>
34+
internal static void WarmModel(Func<DbContext> factory)
35+
{
36+
using var context = factory();
37+
_ = context.Model.GetRelationalModel();
38+
}
2239
}

SqlServerSimulator.Tests.EFCore/EFCoreConcurrencyTokens.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class EFCoreConcurrencyTokens
2020
{
2121
public TestContext TestContext { get; set; } = null!;
2222

23+
[ClassInitialize]
24+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new InventoryContext(new Simulation()));
25+
2326
private sealed class Item
2427
{
2528
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreDate.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public class EFCoreDate
1313
{
1414
public TestContext TestContext { get; set; } = null!;
1515

16+
[ClassInitialize]
17+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new AdapterTestDbContext(new Simulation()));
18+
1619
[TestMethod]
1720
public void Insert_DateOnly_RoundTrips()
1821
{

SqlServerSimulator.Tests.EFCore/EFCoreEagerSplitQueries.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public class EFCoreEagerSplitQueries
1616
{
1717
public TestContext TestContext { get; set; } = null!;
1818

19+
[ClassInitialize]
20+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new BlogContext(new Simulation()));
21+
1922
private sealed class Blog
2023
{
2124
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreExecuteUpdateDelete.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public class EFCoreExecuteUpdateDelete
1919
{
2020
public TestContext TestContext { get; set; } = null!;
2121

22+
[ClassInitialize]
23+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new UserContext(new Simulation()));
24+
2225
private sealed class User
2326
{
2427
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreForeignKey.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public sealed class EFCoreForeignKey
1616
{
1717
public TestContext TestContext { get; set; } = null!;
1818

19+
[ClassInitialize]
20+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new LibraryContext(new Simulation()));
21+
1922
private sealed class Author
2023
{
2124
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreHiLo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public sealed class EFCoreHiLo
1616
{
1717
public TestContext TestContext { get; set; } = null!;
1818

19+
[ClassInitialize]
20+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new HiLoDbContext(new Simulation()));
21+
1922
private class HiLoEntity
2023
{
2124
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreInheritanceTpc.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public class EFCoreInheritanceTpc
1717
{
1818
public TestContext TestContext { get; set; } = null!;
1919

20+
[ClassInitialize]
21+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new PetContext(new Simulation()));
22+
2023
private abstract class Pet
2124
{
2225
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreInheritanceTph.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public class EFCoreInheritanceTph
1818
{
1919
public TestContext TestContext { get; set; } = null!;
2020

21+
[ClassInitialize]
22+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new PetContext(new Simulation()));
23+
2124
private abstract class Pet
2225
{
2326
public int Id { get; set; }

SqlServerSimulator.Tests.EFCore/EFCoreInheritanceTpt.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public class EFCoreInheritanceTpt
1919
{
2020
public TestContext TestContext { get; set; } = null!;
2121

22+
[ClassInitialize]
23+
public static void WarmModel(TestContext _) => AssemblyHooks.WarmModel(() => new PetContext(new Simulation()));
24+
2225
private abstract class Pet
2326
{
2427
public int Id { get; set; }

0 commit comments

Comments
 (0)