diff --git a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs
index 696abeab02b..f54380ef245 100644
--- a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs
@@ -119,7 +119,7 @@ public virtual Encoding OutputEncoding
///
public virtual void Initialize()
{
- _session = null;
+ _session?.Clear();
_errors = null;
_extension = null;
_outputEncoding = null;
diff --git a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs
index cbfb1c59c1b..e6e255c6dd4 100644
--- a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs
@@ -5,7 +5,7 @@
using System.Text;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
-using Engine = Mono.TextTemplating.TemplatingEngine;
+using Mono.TextTemplating;
namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal;
@@ -23,7 +23,7 @@ public class TextTemplatingModelGenerator : TemplatedModelGenerator
private readonly IOperationReporter _reporter;
private readonly IServiceProvider _serviceProvider;
- private Engine? _engine;
+ private TemplatingEngine? _engine;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -47,8 +47,8 @@ public TextTemplatingModelGenerator(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected virtual Engine Engine
- => _engine ??= new Engine();
+ protected virtual TemplatingEngine Engine
+ => _engine ??= new TemplatingEngine();
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -107,7 +107,9 @@ public override ScaffoldedModel GenerateModel(IModel model, ModelCodeGenerationO
{
host.TemplateFile = contextTemplate;
- generatedCode = ProcessTemplate(contextTemplate, host);
+ generatedCode = Engine.ProcessTemplate(File.ReadAllText(contextTemplate), host);
+ CheckEncoding(host.OutputEncoding);
+ HandleErrors(host);
}
else
{
@@ -153,23 +155,41 @@ public override ScaffoldedModel GenerateModel(IModel model, ModelCodeGenerationO
{
host.TemplateFile = entityTypeTemplate;
- foreach (var entityType in model.GetEntityTypes())
+ CompiledTemplate? compiledEntityTypeTemplate = null;
+ string? entityTypeExtension = null;
+ try
{
- host.Initialize();
- host.Session.Add("EntityType", entityType);
- host.Session.Add("Options", options);
- host.Session.Add("NamespaceHint", options.ModelNamespace);
- host.Session.Add("ProjectDefaultNamespace", options.RootNamespace);
-
- generatedCode = ProcessTemplate(entityTypeTemplate, host);
- if (string.IsNullOrWhiteSpace(generatedCode))
+ foreach (var entityType in model.GetEntityTypes())
{
- continue;
- }
+ host.Initialize();
+ host.Session.Add("EntityType", entityType);
+ host.Session.Add("Options", options);
+ host.Session.Add("NamespaceHint", options.ModelNamespace);
+ host.Session.Add("ProjectDefaultNamespace", options.RootNamespace);
+
+ if (compiledEntityTypeTemplate is null)
+ {
+ compiledEntityTypeTemplate = Engine.CompileTemplate(File.ReadAllText(entityTypeTemplate), host);
+ entityTypeExtension = host.Extension;
+ CheckEncoding(host.OutputEncoding);
+ }
+
+ generatedCode = compiledEntityTypeTemplate.Process();
+ HandleErrors(host);
+
+ if (string.IsNullOrWhiteSpace(generatedCode))
+ {
+ continue;
+ }
- var entityTypeFileName = entityType.Name + host.Extension;
- resultingFiles.AdditionalFiles.Add(
- new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode });
+ var entityTypeFileName = entityType.Name + entityTypeExtension;
+ resultingFiles.AdditionalFiles.Add(
+ new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode });
+ }
+ }
+ finally
+ {
+ compiledEntityTypeTemplate?.Dispose();
}
}
@@ -178,54 +198,71 @@ public override ScaffoldedModel GenerateModel(IModel model, ModelCodeGenerationO
{
host.TemplateFile = configurationTemplate;
- foreach (var entityType in model.GetEntityTypes())
+ CompiledTemplate? compiledConfigurationTemplate = null;
+ string? configurationExtension = null;
+ try
{
- host.Initialize();
- host.Session.Add("EntityType", entityType);
- host.Session.Add("Options", options);
- host.Session.Add("NamespaceHint", options.ContextNamespace ?? options.ModelNamespace);
- host.Session.Add("ProjectDefaultNamespace", options.RootNamespace);
-
- generatedCode = ProcessTemplate(configurationTemplate, host);
- if (string.IsNullOrWhiteSpace(generatedCode))
+ foreach (var entityType in model.GetEntityTypes())
{
- continue;
- }
+ host.Initialize();
+ host.Session.Add("EntityType", entityType);
+ host.Session.Add("Options", options);
+ host.Session.Add("NamespaceHint", options.ContextNamespace ?? options.ModelNamespace);
+ host.Session.Add("ProjectDefaultNamespace", options.RootNamespace);
+
+ if (compiledConfigurationTemplate is null)
+ {
+ compiledConfigurationTemplate = Engine.CompileTemplate(File.ReadAllText(configurationTemplate), host);
+ configurationExtension = host.Extension;
+ CheckEncoding(host.OutputEncoding);
+ }
- var configurationFileName = entityType.Name + "Configuration" + host.Extension;
- resultingFiles.AdditionalFiles.Add(
- new ScaffoldedFile
+ generatedCode = compiledConfigurationTemplate.Process();
+ HandleErrors(host);
+
+ if (string.IsNullOrWhiteSpace(generatedCode))
{
- Path = options.ContextDir != null
- ? Path.Combine(options.ContextDir, configurationFileName)
- : configurationFileName,
- Code = generatedCode
- });
+ continue;
+ }
+
+ var configurationFileName = entityType.Name + "Configuration" + configurationExtension;
+ resultingFiles.AdditionalFiles.Add(
+ new ScaffoldedFile
+ {
+ Path = options.ContextDir != null
+ ? Path.Combine(options.ContextDir, configurationFileName)
+ : configurationFileName,
+ Code = generatedCode
+ });
+ }
+ }
+ finally
+ {
+ compiledConfigurationTemplate?.Dispose();
}
}
return resultingFiles;
}
- private string ProcessTemplate(string inputFile, TextTemplatingEngineHost host)
+ private void CheckEncoding(Encoding outputEncoding)
{
- var output = Engine.ProcessTemplate(File.ReadAllText(inputFile), host);
-
- foreach (CompilerError error in host.Errors)
+ if (outputEncoding != Encoding.UTF8)
{
- _reporter.Write(error);
+ _reporter.WriteWarning(DesignStrings.EncodingIgnored(outputEncoding.WebName));
}
+ }
- if (host.OutputEncoding != Encoding.UTF8)
+ private void HandleErrors(TextTemplatingEngineHost host)
+ {
+ foreach (CompilerError error in host.Errors)
{
- _reporter.WriteWarning(DesignStrings.EncodingIgnored(host.OutputEncoding.WebName));
+ _reporter.Write(error);
}
if (host.Errors.HasErrors)
{
- throw new OperationException(DesignStrings.ErrorGeneratingOutput(inputFile));
+ throw new OperationException(DesignStrings.ErrorGeneratingOutput(host.TemplateFile));
}
-
- return output;
}
}
diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs
index 4179112e455..6a0fc2e1124 100644
--- a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs
@@ -3,6 +3,7 @@
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
@@ -326,6 +327,7 @@ public void GenerateModel_uses_output_extension()
var generator = CreateGenerator();
var model = new ModelBuilder()
.Entity("Entity1", b => { })
+ .Entity("Entity2", b => { })
.FinalizeModel();
var result = generator.GenerateModel(
@@ -339,9 +341,11 @@ public void GenerateModel_uses_output_extension()
Assert.Equal("Context.vb", result.ContextFile.Path);
- Assert.Equal(2, result.AdditionalFiles.Count);
+ Assert.Equal(4, result.AdditionalFiles.Count);
Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1.fs");
+ Assert.Single(result.AdditionalFiles, f => f.Path == "Entity2.fs");
Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1Configuration.py");
+ Assert.Single(result.AdditionalFiles, f => f.Path == "Entity2Configuration.py");
}
[ConditionalFact]
@@ -387,8 +391,7 @@ public void GenerateModel_reports_errors()
Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate));
File.WriteAllText(
contextTemplate,
- @"<# Warning(""This is a warning"");
-Error(""This is an error""); #>");
+ @"<# Error(""This is an error""); #>");
var reporter = new TestOperationReporter();
var generator = CreateGenerator(reporter);
@@ -407,17 +410,64 @@ public void GenerateModel_reports_errors()
Assert.Equal(DesignStrings.ErrorGeneratingOutput(contextTemplate), ex.Message);
+ Assert.Collection(
+ reporter.Messages,
+ x =>
+ {
+ Assert.Equal(LogLevel.Error, x.Level);
+ Assert.Contains("This is an error", x.Message);
+ });
+ }
+
+ [ConditionalFact]
+ public void GenerateModel_reports_warnings()
+ {
+ using var projectDir = new TempDirectory();
+
+ var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4");
+ Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate));
+ File.WriteAllText(
+ contextTemplate,
+ @"<# Warning(""Warning about DbContext""); #>");
+ var entityTypeTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4");
+ File.WriteAllText(
+ entityTypeTemplate,
+ @"<#@ assembly name=""Microsoft.EntityFrameworkCore"" #>
+<#@ parameter name=""EntityType"" type=""Microsoft.EntityFrameworkCore.Metadata.IEntityType"" #>
+<# Warning(""Warning about "" + EntityType.Name); #>");
+
+ var reporter = new TestOperationReporter();
+ var generator = CreateGenerator(reporter);
+ var model = new ModelBuilder()
+ .Entity("Entity1", b => { })
+ .Entity("Entity2", b => { })
+ .FinalizeModel();
+
+ var result = generator.GenerateModel(
+ model,
+ new()
+ {
+ ContextName = "Context",
+ ConnectionString = @"Name=DefaultConnection",
+ ProjectDir = projectDir
+ });
+
Assert.Collection(
reporter.Messages,
x =>
{
Assert.Equal(LogLevel.Warning, x.Level);
- Assert.Contains("This is a warning", x.Message);
+ Assert.Contains("Warning about DbContext", x.Message);
},
x =>
{
- Assert.Equal(LogLevel.Error, x.Level);
- Assert.Contains("This is an error", x.Message);
+ Assert.Equal(LogLevel.Warning, x.Level);
+ Assert.Contains("Warning about Entity1", x.Message);
+ },
+ x =>
+ {
+ Assert.Equal(LogLevel.Warning, x.Level);
+ Assert.Contains("Warning about Entity2", x.Message);
});
}