From 2e8cb39ed12d7419ffd8e4646a2ceaf0456dd79b Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sun, 7 Apr 2024 12:34:53 +0100 Subject: [PATCH 1/2] Add auto-compiled models to What's New --- .../core/what-is-new/ef-core-9.0/whatsnew.md | 118 ++++++++++++++++++ .../App/App.csproj | 22 ++++ .../App/Program.cs | 13 ++ .../Model/BlogsContext.cs | 14 +++ .../Model/EntityTypes.cs | 16 +++ .../Model/Model.csproj | 39 ++++++ .../NewInEFCore9/ExecuteUpdateSample.cs | 13 +- .../NewInEFCore9/NewInEFCore9.csproj | 18 +-- samples/core/Samples.sln | 17 +++ 9 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/App.csproj create mode 100644 samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs create mode 100644 samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/BlogsContext.cs create mode 100644 samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/EntityTypes.cs create mode 100644 samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/Model.csproj diff --git a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md index 13b4d68282..3a797717e8 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md @@ -562,6 +562,124 @@ protected override void Up(MigrationBuilder migrationBuilder) ## Model building + + +### Auto-compiled models + +> [!TIP] +> The code shown here comes from the [NewInEFCore9.CompiledModels](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/) sample. + +Compiled models can improve startup time for applications with large models--that is entity type counts in the 100s or 1000s. In previous versions of EF Core, a compiled model had to be generated manually, using the command line. For example: + +```dotnetcli +dotnet ef dbcontext optimize +``` + +After running the command, a line like, `.UseModel(MyCompiledModels.BlogsContextModel.Instance)` must be added to `OnConfiguring` to tell EF Core to use the compiled model. + +Starting with EF9, this `.UseModel` line is no longer needed. Instead, the compiled model will be detected and used automatically. This can be seen by having EF log whenever it is building the model. Running a simple application then shows EF building the model when the application starts: + +```output +Starting application... +>> EF is building the model... +Model loaded model with 2 entity types. +``` + +The output from running `dotnet ef dbcontext optimize` on the model project is: + +```output +PS D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model> dotnet ef dbcontext optimize + +Build succeeded in 0.3s + +Build succeeded in 0.3s +Build started... +Build succeeded. +>> EF is building the model... +>> EF is building the model... +Successfully generated a compiled model, it will be discovered automatically, but you can also call 'options.UseModel(BlogsContextModel.Instance)'. Run this command again when the model is modified. +PS D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model> +``` + +Notice that the log output indicates that the _model was built when running the command_. If we now run the application again, after rebuilding but without making any code changes, then the output is: + +```output +Starting application... +Model loaded model with 2 entity types. +``` + +Notice that the model was not built when starting the application because the compiled model was detected and used automatically. + +### MSBuild integration + +With the above approach, the compiled model still needs to be regenerated manually when the entity types or `DbContext` configuration is changed. However, EF9 ships with MSBuild and targets package that can automatically update the compiled model when the model project is built! To get started, install the [Microsoft.EntityFrameworkCore.Tasks](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Tasks/) NuGet package. For example: + +```dotnetcli +dotnet add package Microsoft.EntityFrameworkCore.Tasks --version 9.0.0-preview.4.24205.3 +``` + +> [!TIP] +> Use the package version in the command above that matches the version of EF Core that you are using. + +Then enable the integration by setting the `EFOptimizeContext` property to your `.csproj` file. For example: + +```xml + + true + +``` + +There are additional, optional, MSBuild properties for controlling how the model is built, equivalent to the options passed on the command line to `dotnet ef dbcontext optimize`. These include: + +| MSBuild property | Description | +|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| EFOptimizeContext | Set to `true` to enable auto-compiled models. | +| DbContextName | The DbContext class to use. Class name only or fully qualified with namespaces. If this option is omitted, EF Core will find the context class. If there are multiple context classes, this option is required. | +| EFStartupProject | Relative path to the project folder of the startup project. Default value is the current folder. | +| EFTargetNamespace | The namespace to use for all generated classes. Defaults to generated from the root namespace and the output directory plus CompiledModels. | + +In our example, we need to specify the startup project: + +```xml + + true + ..\App\App.csproj + +``` + +Now, if we build the project, we can see logging at build time indicating that the compiled model is being built: + +```output +Optimizing DbContext... +dotnet exec --depsfile D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.deps.json + --additionalprobingpath G:\packages + --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" + --runtimeconfig D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.runtimeconfig.json G:\packages\microsoft.entityframeworkcore.tasks\9.0.0-preview.4.24205.3\tasks\net8.0\..\..\tools\netcoreapp2.0\ef.dll dbcontext optimize --output-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model\obj\Release\net8.0\ + --namespace NewInEfCore9 + --suffix .g + --assembly D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model\bin\Release\net8.0\Model.dll --startup-assembly D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App\bin\Release\net8.0\App.dll + --project-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\Model + --root-namespace NewInEfCore9 + --language C# + --nullable + --working-dir D:\code\EntityFramework.Docs\samples\core\Miscellaneous\NewInEFCore9.CompiledModels\App + --verbose + --no-color + --prefix-output +``` + +And running the application shows that the compiled model has been detected and hence the model is not built again: + +```output +Starting application... +Model loaded model with 2 entity types. +``` + +Now, whenever the model changes, the compiled model will be automatically rebuilt as soon as the project is built. + +> [NOTE!] +> We are working through some performance issues with changes made to the compiled model in EF8 and EF9. See [Issue 33483#](https://github.com/dotnet/efcore/issues/33483) for more information. + ### Specify caching for sequences diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/App.csproj b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/App.csproj new file mode 100644 index 0000000000..20df067599 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/App.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs new file mode 100644 index 0000000000..1529e6b505 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs @@ -0,0 +1,13 @@ +using NewInEfCore9; + +public class Program +{ + public static void Main() + { + Console.WriteLine("Starting application..."); + + using var context = new BlogsContext(); + + Console.WriteLine($"Model loaded model with {context.Model.GetEntityTypes().Count()} entity types."); + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/BlogsContext.cs b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/BlogsContext.cs new file mode 100644 index 0000000000..af044eb4a5 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/BlogsContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace NewInEfCore9; + +public class BlogsContext : DbContext +{ + public DbSet Blogs => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EF9CompiledModels;ConnectRetryCount=0") + .LogTo(_ => Console.WriteLine(">> EF is building the model..."), [CoreEventId.ShadowPropertyCreated]) + .EnableSensitiveDataLogging(); +} diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/EntityTypes.cs b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/EntityTypes.cs new file mode 100644 index 0000000000..68749a3845 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/EntityTypes.cs @@ -0,0 +1,16 @@ +namespace NewInEfCore9; + +public class Blog +{ + public int Id { get; set; } + + public ICollection Posts { get; } = new List(); +} + +public class Post +{ + public int Id { get; set; } + public string? Title { get; set; } + + public Blog Blog { get; set; } = null!; +} diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/Model.csproj b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/Model.csproj new file mode 100644 index 0000000000..92369134bc --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/Model/Model.csproj @@ -0,0 +1,39 @@ + + + + library + net8.0 + enable + enable + NewInEfCore9 + Preview + + + + true + ..\App\App.csproj + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + diff --git a/samples/core/Miscellaneous/NewInEFCore9/ExecuteUpdateSample.cs b/samples/core/Miscellaneous/NewInEFCore9/ExecuteUpdateSample.cs index 35dc53eedc..6dbbf336e6 100644 --- a/samples/core/Miscellaneous/NewInEFCore9/ExecuteUpdateSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore9/ExecuteUpdateSample.cs @@ -214,10 +214,19 @@ public class CustomerContext(bool useSqlite = false) : DbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => (UseSqlite - ? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db") + ? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db", + sqliteOptionsBuilder => + { + sqliteOptionsBuilder.UseNetTopologySuite(); + sqliteOptionsBuilder.CommandTimeout(0); + }) : optionsBuilder.UseSqlServer( @$"Server=(localdb)\mssqllocaldb;Database={GetType().Name};ConnectRetryCount=0", - sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite())) + sqlServerOptionsBuilder => + { + sqlServerOptionsBuilder.UseNetTopologySuite(); + sqlServerOptionsBuilder.CommandTimeout(0); + })) .EnableSensitiveDataLogging() .LogTo(Console.WriteLine, LogLevel.Information); diff --git a/samples/core/Miscellaneous/NewInEFCore9/NewInEFCore9.csproj b/samples/core/Miscellaneous/NewInEFCore9/NewInEFCore9.csproj index 29b8b89380..45bacb8f74 100644 --- a/samples/core/Miscellaneous/NewInEFCore9/NewInEFCore9.csproj +++ b/samples/core/Miscellaneous/NewInEFCore9/NewInEFCore9.csproj @@ -10,15 +10,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/samples/core/Samples.sln b/samples/core/Samples.sln index 71587ef8bf..13b6ced270 100644 --- a/samples/core/Samples.sln +++ b/samples/core/Samples.sln @@ -197,6 +197,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewInEFCore8", "Miscellaneo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NewInEFCore9", "Miscellaneous\NewInEFCore9\NewInEFCore9.csproj", "{3F17B59C-7D33-4BF0-B386-ACFE76D45697}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NewInEFCore9.CompiledModels", "NewInEFCore9.CompiledModels", "{26184A10-7CF8-4ED2-9F70-E00A55BE063B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "Miscellaneous\NewInEFCore9.CompiledModels\App\App.csproj", "{22548A1B-0833-49E9-A04E-A7E8690EAE03}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Miscellaneous\NewInEFCore9.CompiledModels\Model\Model.csproj", "{8E9DD759-B6D0-4424-BC6C-97ECE559CE02}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -547,6 +553,14 @@ Global {3F17B59C-7D33-4BF0-B386-ACFE76D45697}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F17B59C-7D33-4BF0-B386-ACFE76D45697}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F17B59C-7D33-4BF0-B386-ACFE76D45697}.Release|Any CPU.Build.0 = Release|Any CPU + {22548A1B-0833-49E9-A04E-A7E8690EAE03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22548A1B-0833-49E9-A04E-A7E8690EAE03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22548A1B-0833-49E9-A04E-A7E8690EAE03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22548A1B-0833-49E9-A04E-A7E8690EAE03}.Release|Any CPU.Build.0 = Release|Any CPU + {8E9DD759-B6D0-4424-BC6C-97ECE559CE02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E9DD759-B6D0-4424-BC6C-97ECE559CE02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E9DD759-B6D0-4424-BC6C-97ECE559CE02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E9DD759-B6D0-4424-BC6C-97ECE559CE02}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -633,6 +647,9 @@ Global {72C964FF-07C3-4234-B277-D91C3D83BAEC} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} {F9CA30FF-70A9-4EA5-8F12-7B08A695AB8F} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} {3F17B59C-7D33-4BF0-B386-ACFE76D45697} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} + {26184A10-7CF8-4ED2-9F70-E00A55BE063B} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} + {22548A1B-0833-49E9-A04E-A7E8690EAE03} = {26184A10-7CF8-4ED2-9F70-E00A55BE063B} + {8E9DD759-B6D0-4424-BC6C-97ECE559CE02} = {26184A10-7CF8-4ED2-9F70-E00A55BE063B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {20C98D35-54EF-46A6-8F3B-1855C1AE4F70} From 428ea23c1ee3ca68528b7e8c049ec383b3bd05cc Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Wed, 10 Apr 2024 11:41:25 +0100 Subject: [PATCH 2/2] API review changes --- .../core/what-is-new/ef-core-9.0/whatsnew.md | 10 +++++----- .../NewInEFCore9.CompiledModels/App/Program.cs | 2 +- samples/core/Samples.sln.DotSettings | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md index 3a797717e8..0091fd8bc6 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md @@ -577,12 +577,12 @@ dotnet ef dbcontext optimize After running the command, a line like, `.UseModel(MyCompiledModels.BlogsContextModel.Instance)` must be added to `OnConfiguring` to tell EF Core to use the compiled model. -Starting with EF9, this `.UseModel` line is no longer needed. Instead, the compiled model will be detected and used automatically. This can be seen by having EF log whenever it is building the model. Running a simple application then shows EF building the model when the application starts: +Starting with EF9, this `.UseModel` line is no longer needed when the application's `DbContext` type is in the same project/assembly as the compiled model. Instead, the compiled model will be detected and used automatically. This can be seen by having EF log whenever it is building the model. Running a simple application then shows EF building the model when the application starts: ```output Starting application... >> EF is building the model... -Model loaded model with 2 entity types. +Model loaded with 2 entity types. ``` The output from running `dotnet ef dbcontext optimize` on the model project is: @@ -605,7 +605,7 @@ Notice that the log output indicates that the _model was built when running the ```output Starting application... -Model loaded model with 2 entity types. +Model loaded with 2 entity types. ``` Notice that the model was not built when starting the application because the compiled model was detected and used automatically. @@ -635,7 +635,7 @@ There are additional, optional, MSBuild properties for controlling how the model |--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | EFOptimizeContext | Set to `true` to enable auto-compiled models. | | DbContextName | The DbContext class to use. Class name only or fully qualified with namespaces. If this option is omitted, EF Core will find the context class. If there are multiple context classes, this option is required. | -| EFStartupProject | Relative path to the project folder of the startup project. Default value is the current folder. | +| EFStartupProject | Relative path to the startup project. Default value is the current folder. | | EFTargetNamespace | The namespace to use for all generated classes. Defaults to generated from the root namespace and the output directory plus CompiledModels. | In our example, we need to specify the startup project: @@ -672,7 +672,7 @@ And running the application shows that the compiled model has been detected and ```output Starting application... -Model loaded model with 2 entity types. +Model loaded with 2 entity types. ``` Now, whenever the model changes, the compiled model will be automatically rebuilt as soon as the project is built. diff --git a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs index 1529e6b505..e951ea4fb4 100644 --- a/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs +++ b/samples/core/Miscellaneous/NewInEFCore9.CompiledModels/App/Program.cs @@ -8,6 +8,6 @@ public static void Main() using var context = new BlogsContext(); - Console.WriteLine($"Model loaded model with {context.Model.GetEntityTypes().Count()} entity types."); + Console.WriteLine($"Model loaded with {context.Model.GetEntityTypes().Count()} entity types."); } } diff --git a/samples/core/Samples.sln.DotSettings b/samples/core/Samples.sln.DotSettings index 0d90266523..8da6af6279 100644 --- a/samples/core/Samples.sln.DotSettings +++ b/samples/core/Samples.sln.DotSettings @@ -115,6 +115,7 @@ $object$_On$event$ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Public" Description="Test Methods"><ElementKinds><Kind Name="TEST_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="Aa_bb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> $object$_On$event$ @@ -135,6 +136,7 @@ True True True + True True True True