diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
index 14a7e6651f7..8c11c285844 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
@@ -164,24 +164,18 @@ private string CreateModel(
using (mainBuilder.Indent())
{
mainBuilder
- .Append("private static ").Append(className).AppendLine(nullable ? "? _instance;" : " _instance;")
- .Append("public static IModel Instance")
- .AppendLines(@"
+ .Append("static ").Append(className).Append("()")
+ .AppendLines(
+ @"
{
- get
- {
- if (_instance == null)
- {
- _instance = new " + className + @"();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
-}");
-
- mainBuilder
+ var model = new " + className + @"();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
+}")
+ .AppendLine()
+ .Append("private static ").Append(className).AppendLine(nullable ? "? _instance;" : " _instance;")
+ .AppendLine("public static IModel Instance => _instance;")
.AppendLine()
.AppendLine("partial void Initialize();")
.AppendLine()
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs
index 377dbcc2795..e350afda1ce 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs
@@ -57,7 +57,7 @@ protected override void InitializeModel(IModel model, bool designTime, bool prev
{
if (prevalidation)
{
- model.AddRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies, RelationalDependencies.RelationalModelDependencies);
+ model.SetRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies, RelationalDependencies.RelationalModelDependencies);
}
else
{
diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs
index 1f31ae74769..15b6091c6ee 100644
--- a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs
+++ b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs
@@ -25,6 +25,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure
///
public class ModelRuntimeInitializer : IModelRuntimeInitializer
{
+ private static readonly object _syncObject = new();
+
///
/// Creates a new instance.
///
@@ -56,59 +58,55 @@ public virtual IModel Initialize(
if (model is Model mutableModel
&& !mutableModel.IsReadOnly)
{
- model = mutableModel.FinalizeModel();
+ lock (_syncObject)
+ {
+ if (!mutableModel.IsReadOnly)
+ {
+ model = mutableModel.FinalizeModel();
+ }
+ }
}
if (model.ModelDependencies == null)
{
- model = model.GetOrAddRuntimeAnnotationValue(
- CoreAnnotationNames.ReadOnlyModel,
- static args =>
+ // Make sure InitializeModel really only gets called once, since it may not be thread safe or idempotent.
+ lock (_syncObject)
+ {
+ if (model.ModelDependencies == null)
{
- var (initializer, model, designTime, validationLogger) = args;
+ model.ModelDependencies = Dependencies.ModelDependencies;
- model.ModelDependencies = initializer.Dependencies.ModelDependencies;
-
- initializer.InitializeModel(model, designTime, prevalidation: true);
+ InitializeModel(model, designTime, prevalidation: true);
if (validationLogger != null
&& model is IConventionModel)
{
- initializer.Dependencies.ModelValidator.Validate(model, validationLogger);
- }
-
- initializer.InitializeModel(model, designTime, prevalidation: false);
-
- if (!designTime
- && model is Model mutableModel)
- {
- model = mutableModel.OnModelFinalized();
+ Dependencies.ModelValidator.Validate(model, validationLogger);
}
- return model;
- },
- (this, model, designTime, validationLogger));
-
- if (designTime)
- {
- model.RemoveRuntimeAnnotation(CoreAnnotationNames.ReadOnlyModel);
+ InitializeModel(model, designTime, prevalidation: false);
+ }
}
}
- else if (!designTime)
+
+ if (designTime)
{
- model = model.GetOrAddRuntimeAnnotationValue(
- CoreAnnotationNames.ReadOnlyModel,
- static model =>
+ return model;
+ }
+
+ model = model.GetOrAddRuntimeAnnotationValue(
+ CoreAnnotationNames.ReadOnlyModel,
+ static model =>
+ {
+ if (model is Model mutableModel)
{
- if (model is Model mutableModel)
- {
- model = mutableModel.OnModelFinalized();
- }
+ // This assumes OnModelFinalized is thread-safe
+ model = mutableModel.OnModelFinalized();
+ }
- return model!;
- },
- model);
- }
+ return model!;
+ },
+ model);
return model;
}
diff --git a/src/EFCore/Metadata/Conventions/IModelFinalizedConvention.cs b/src/EFCore/Metadata/Conventions/IModelFinalizedConvention.cs
index fa5e4652e1a..70f524a9d94 100644
--- a/src/EFCore/Metadata/Conventions/IModelFinalizedConvention.cs
+++ b/src/EFCore/Metadata/Conventions/IModelFinalizedConvention.cs
@@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
public interface IModelFinalizedConvention : IConvention
{
///
- /// Called after a model is finalized and can no longer be mutated.
+ /// Called after a model is finalized and can no longer be mutated.
+ /// The implementation must be thread-safe.
///
/// The model.
IModel ProcessModelFinalized(IModel model);
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
index 01d1e8c1456..7f436fa4768 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs
@@ -55,22 +55,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.EmptyContext))]
partial class EmptyContextModel : RuntimeModel
{
- private static EmptyContextModel _instance;
- public static IModel Instance
+ static EmptyContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new EmptyContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new EmptyContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static EmptyContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -476,22 +471,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.BigContext))]
partial class BigContextModel : RuntimeModel
{
- private static BigContextModel? _instance;
- public static IModel Instance
+ static BigContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new BigContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new BigContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static BigContextModel? _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -1853,22 +1843,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext))]
partial class DbFunctionContextModel : RuntimeModel
{
- private static DbFunctionContextModel _instance;
- public static IModel Instance
+ static DbFunctionContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new DbFunctionContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new DbFunctionContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static DbFunctionContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -2343,22 +2328,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SequencesContext))]
partial class SequencesContextModel : RuntimeModel
{
- private static SequencesContextModel _instance;
- public static IModel Instance
+ static SequencesContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new SequencesContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new SequencesContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static SequencesContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -2551,22 +2531,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.ConstraintsContext))]
partial class ConstraintsContextModel : RuntimeModel
{
- private static ConstraintsContextModel _instance;
- public static IModel Instance
+ static ConstraintsContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new ConstraintsContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new ConstraintsContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static ConstraintsContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -2707,22 +2682,17 @@ namespace Microsoft.EntityFrameworkCore.Metadata
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SqliteContext))]
partial class SqliteContextModel : RuntimeModel
{
- private static SqliteContextModel _instance;
- public static IModel Instance
+ static SqliteContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new SqliteContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new SqliteContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static SqliteContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
@@ -2887,22 +2857,17 @@ namespace TestNamespace
[DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.CosmosContext))]
partial class CosmosContextModel : RuntimeModel
{
- private static CosmosContextModel _instance;
- public static IModel Instance
+ static CosmosContextModel()
{
- get
- {
- if (_instance == null)
- {
- _instance = new CosmosContextModel();
- _instance.Initialize();
- _instance.Customize();
- }
-
- return _instance;
- }
+ var model = new CosmosContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
}
+ private static CosmosContextModel _instance;
+ public static IModel Instance => _instance;
+
partial void Initialize();
partial void Customize();
diff --git a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs
index a0a122fe984..59e991c60fc 100644
--- a/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs
+++ b/test/EFCore.SqlServer.Tests/SqlServerOptionsExtensionTest.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.Extensions.DependencyInjection;
@@ -11,6 +13,54 @@ namespace Microsoft.EntityFrameworkCore
{
public class SqlServerOptionsExtensionTest
{
+ [ConditionalFact]
+ public void Compiled_model_is_thread_safe()
+ {
+ var tasks = new Task[Environment.ProcessorCount];
+ for (var i = 0; i < tasks.Length; i++)
+ {
+ tasks[i] = Task.Factory.StartNew(() =>
+ {
+ using (var ctx = new EmptyContext())
+ {
+ Assert.NotNull(ctx.Model.GetRelationalDependencies());
+ }
+ });
+ }
+
+ Task.WaitAll(tasks);
+ }
+
+ private class EmptyContext : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
+ {
+ optionsBuilder.UseSqlServer().UseModel(EmptyContextModel.Instance);
+ }
+ }
+ }
+
+ [DbContext(typeof(EmptyContext))]
+ partial class EmptyContextModel : RuntimeModel
+ {
+ static EmptyContextModel()
+ {
+ var model = new EmptyContextModel();
+ model.Initialize();
+ model.Customize();
+ _instance = model;
+ }
+
+ private static readonly EmptyContextModel _instance;
+ public static IModel Instance => _instance;
+
+ partial void Initialize();
+
+ partial void Customize();
+ }
+
[ConditionalFact]
public void ApplyServices_adds_SQL_server_services()
{