From 41f3772d3730f9109ee9e3248be61a7d288c1477 Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Tue, 27 Oct 2020 12:12:41 -0700 Subject: [PATCH] Document passing design-time args Additional changes: - Add tips about EXEC (fixes #2561) - Add a tab for Get-Migration - Update custom operation samples (fixes #1574) - Fix some bad PMC examples (fixes #2296) - Add examples using Name= (fixes #2145) Fixes #1050 --- .../managing-schemas/migrations/managing.md | 18 ++- .../managing-schemas/migrations/operations.md | 109 ++---------------- .../miscellaneous/cli/dbcontext-creation.md | 26 ++++- .../core/miscellaneous/cli/dotnet.md | 20 +++- .../core/miscellaneous/cli/powershell.md | 28 +++-- .../Schemas/Migrations/CustomOperation.cs | 88 ++++++++++++++ .../Migrations/CustomOperationMultiSql.cs | 28 +++++ .../Schemas/Migrations/CustomOperationSql.cs | 14 +++ 8 files changed, 219 insertions(+), 112 deletions(-) create mode 100644 samples/core/Schemas/Migrations/CustomOperation.cs create mode 100644 samples/core/Schemas/Migrations/CustomOperationMultiSql.cs create mode 100644 samples/core/Schemas/Migrations/CustomOperationSql.cs diff --git a/entity-framework/core/managing-schemas/migrations/managing.md b/entity-framework/core/managing-schemas/migrations/managing.md index 5ec870c755..c07a3d3f4d 100644 --- a/entity-framework/core/managing-schemas/migrations/managing.md +++ b/entity-framework/core/managing-schemas/migrations/managing.md @@ -2,7 +2,7 @@ title: Managing Migrations - EF Core description: Adding, removing and otherwise managing database schema migrations with Entity Framework Core author: bricelam -ms.date: 05/06/2020 +ms.date: 10/27/2020 uid: core/managing-schemas/migrations/managing --- # Managing Migrations @@ -147,6 +147,9 @@ migrationBuilder.Sql( RETURN @LastName + @FirstName;')"); ``` +> [!TIP] +> `EXEC` is used when a statement must be the first or only one in a SQL batch. It can also be used to work around parser errors in idempotent migration scripts that can occur when referenced columns don't currently exist on a table. + This can be used to manage any aspect of your database, including: * Stored procedures @@ -186,10 +189,23 @@ After removing the migration, you can make the additional model changes and add You can list all existing migrations as follows: +### [.NET Core CLI](#tab/dotnet-core-cli) + ```dotnetcli dotnet ef migrations list ``` +### [Visual Studio](#tab/vs) + +> [!NOTE] +> This command was added in EF Core 5.0. + +```powershell +Get-Migration +``` + +*** + ## Resetting all migrations In some extreme cases, it may be necessary to remove all migrations and start over. This can be easily done by deleting your **Migrations** folder and dropping your database; at that point you can create a new initial migration, which will contain you entire current schema. diff --git a/entity-framework/core/managing-schemas/migrations/operations.md b/entity-framework/core/managing-schemas/migrations/operations.md index 83cf3f4831..f15e366fb6 100644 --- a/entity-framework/core/managing-schemas/migrations/operations.md +++ b/entity-framework/core/managing-schemas/migrations/operations.md @@ -2,7 +2,7 @@ title: Custom Migrations Operations - EF Core description: Managing custom and raw SQL migrations for database schema management with Entity Framework Core author: bricelam -ms.date: 11/07/2017 +ms.date: 10/27/2020 uid: core/managing-schemas/migrations/operations --- # Custom Migrations Operations @@ -19,36 +19,14 @@ migrationBuilder.CreateUser("SQLUser1", "Password"); The easiest way to implement a custom operation is to define an extension method that calls `MigrationBuilder.Sql()`. Here is an example that generates the appropriate Transact-SQL. -```csharp -static MigrationBuilder CreateUser( - this MigrationBuilder migrationBuilder, - string name, - string password) - => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';"); -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperationSql.cs#snippet_CustomOperationSql)] + +> [!TIP] +> Use the `EXEC` function when a statement must be the first or only one in a SQL batch. It might also be needed to work around parser errors in idempotent migration scripts that can occur when referenced columns don't currently exist on a table. If your migrations need to support multiple database providers, you can use the `MigrationBuilder.ActiveProvider` property. Here's an example supporting both Microsoft SQL Server and PostgreSQL. -```csharp -static MigrationBuilder CreateUser( - this MigrationBuilder migrationBuilder, - string name, - string password) -{ - switch (migrationBuilder.ActiveProvider) - { - case "Npgsql.EntityFrameworkCore.PostgreSQL": - return migrationBuilder - .Sql($"CREATE USER {name} WITH PASSWORD '{password}';"); - - case "Microsoft.EntityFrameworkCore.SqlServer": - return migrationBuilder - .Sql($"CREATE USER {name} WITH PASSWORD = '{password}';"); - } - - return migrationBuilder; -} -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperationMultiSql.cs#snippet_CustomOperationMultiSql)] This approach only works if you know every provider where your custom operation will be applied. @@ -56,83 +34,16 @@ This approach only works if you know every provider where your custom operation To decouple the custom operation from the SQL, you can define your own `MigrationOperation` to represent it. The operation is then passed to the provider so it can determine the appropriate SQL to generate. -```csharp -class CreateUserOperation : MigrationOperation -{ - public string Name { get; set; } - public string Password { get; set; } -} -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperation.cs#snippet_CreateUserOperation)] With this approach, the extension method just needs to add one of these operations to `MigrationBuilder.Operations`. -```csharp -static MigrationBuilder CreateUser( - this MigrationBuilder migrationBuilder, - string name, - string password) -{ - migrationBuilder.Operations.Add( - new CreateUserOperation - { - Name = name, - Password = password - }); - - return migrationBuilder; -} -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperation.cs#snippet_MigrationBuilderExtension)] This approach requires each provider to know how to generate SQL for this operation in their `IMigrationsSqlGenerator` service. Here is an example overriding the SQL Server's generator to handle the new operation. -```csharp -class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator -{ - public MyMigrationsSqlGenerator( - MigrationsSqlGeneratorDependencies dependencies, - IMigrationsAnnotationProvider migrationsAnnotations) - : base(dependencies, migrationsAnnotations) - { - } - - protected override void Generate( - MigrationOperation operation, - IModel model, - MigrationCommandListBuilder builder) - { - if (operation is CreateUserOperation createUserOperation) - { - Generate(createUserOperation, builder); - } - else - { - base.Generate(operation, model, builder); - } - } - - private void Generate( - CreateUserOperation operation, - MigrationCommandListBuilder builder) - { - var sqlHelper = Dependencies.SqlGenerationHelper; - var stringMapping = Dependencies.TypeMappingSource.FindMapping(typeof(string)); - - builder - .Append("CREATE USER ") - .Append(sqlHelper.DelimitIdentifier(operation.Name)) - .Append(" WITH PASSWORD = ") - .Append(stringMapping.GenerateSqlLiteral(operation.Password)) - .AppendLine(sqlHelper.StatementTerminator) - .EndCommand(); - } -} -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperation.cs#snippet_MigrationsSqlGenerator)] Replace the default migrations sql generator service with the updated one. -```csharp -protected override void OnConfiguring(DbContextOptionsBuilder options) - => options - .UseSqlServer(connectionString) - .ReplaceService(); -``` +[!code-csharp[](../../../../samples/core/Schemas/Migrations/CustomOperation.cs#snippet_OnConfiguring)] diff --git a/entity-framework/core/miscellaneous/cli/dbcontext-creation.md b/entity-framework/core/miscellaneous/cli/dbcontext-creation.md index 97d1305908..3de466a274 100644 --- a/entity-framework/core/miscellaneous/cli/dbcontext-creation.md +++ b/entity-framework/core/miscellaneous/cli/dbcontext-creation.md @@ -2,7 +2,7 @@ title: Design-time DbContext Creation - EF Core description: Strategies for creating a design-time DbContext with Entity Framework Core author: bricelam -ms.date: 09/16/2019 +ms.date: 10/27/2020 uid: core/miscellaneous/cli/dbcontext-creation --- # Design-time DbContext Creation @@ -39,7 +39,29 @@ You can also tell the tools how to create your DbContext by implementing the `ID > This is fixed in EFCore 5.0 and any additional design-time arguments > are passed into the application through that parameter. -A design-time factory can be especially useful if you need to configure the DbContext differently for design time than at run time, if the `DbContext` constructor takes additional parameters are not registered in DI, if you are not using DI at all, or if for some reason you prefer not to have a `BuildWebHost` method in your ASP.NET Core application's `Main` class. +A design-time factory can be especially useful if you need to configure the DbContext differently for design time than at run time, if the `DbContext` constructor takes additional parameters are not registered in DI, if you are not using DI at all, or if for some reason you prefer not to have a `CreateHostBuilder` method in your ASP.NET Core application's `Main` class. + +## Args + +Both IDesignTimeDbContextFactory.CreateDbContext and Program.CreateHostBuilder accept command line arguments. + +Starting in EF Core 5.0, you can specify these arguments from the tools: + +### [.NET Core CLI](#tab/dotnet-core-cli) + +```dotnetcli +dotnet ef database update -- --environment Production +``` + +The `--` token directs `dotnet ef` to treat everything that follows as an argument and not try to parse them as options. Any extra arguments not used by `dotnet ef` are forwarded to the app. + +### [Visual Studio](#tab/vs) + +```powershell +Update-Database -Args '--environment Production' +``` + +*** [1]: xref:core/managing-schemas/migrations/index [2]: xref:core/miscellaneous/configuring-dbcontext diff --git a/entity-framework/core/miscellaneous/cli/dotnet.md b/entity-framework/core/miscellaneous/cli/dotnet.md index 8064dcf5b7..4ea5c06b2a 100644 --- a/entity-framework/core/miscellaneous/cli/dotnet.md +++ b/entity-framework/core/miscellaneous/cli/dotnet.md @@ -2,7 +2,7 @@ title: EF Core tools reference (.NET CLI) - EF Core description: Reference guide for the Entity Framework Core .NET Core CLI tools author: bricelam -ms.date: 10/13/2020 +ms.date: 10/27/2020 uid: core/miscellaneous/cli/dotnet --- @@ -93,7 +93,16 @@ Why is a dummy project required? As mentioned earlier, the tools have to execute ### ASP.NET Core environment -To specify the environment for ASP.NET Core projects, set the **ASPNETCORE_ENVIRONMENT** environment variable before running commands. +To specify [the environment](/aspnet/core/fundamentals/environments) for ASP.NET Core projects, set the **ASPNETCORE_ENVIRONMENT** environment variable before running commands. + +Starting in EF Core 5.0, additional arguments can also be passed into Program.CreateHostBuilder allowing you to specify the environment on the command-line: + +```dotnetcli +dotnet ef database update -- --environment Production +``` + +> [!TIP] +> The `--` token directs `dotnet ef` to treat everything that follows as an argument and not try to parse them as options. Any extra arguments not used by `dotnet ef` are forwarded to the app. ## Common options @@ -206,6 +215,13 @@ The following example scaffolds only selected tables and creates the context in dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -t Blog -t Post --context-dir Context -c BlogContext --context-namespace New.Namespace ``` +The following example reads the connection string from the project's configuration set using the [Secret Manager tool](/aspnet/core/security/app-secrets#secret-manager). + +```dotnetcli +dotnet user-secrets set ConnectionStrings.Blogging "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Blogging" +dotnet ef dbcontext scaffold Name=ConnectionStrings.Blogging Microsoft.EntityFrameworkCore.SqlServer +``` + ## dotnet ef dbcontext script Generates a SQL script from the DbContext. Bypasses any migrations. Added in EF Core 3.0. diff --git a/entity-framework/core/miscellaneous/cli/powershell.md b/entity-framework/core/miscellaneous/cli/powershell.md index 0a286d40e3..381aad94e3 100644 --- a/entity-framework/core/miscellaneous/cli/powershell.md +++ b/entity-framework/core/miscellaneous/cli/powershell.md @@ -2,7 +2,7 @@ title: EF Core tools reference (Package Manager Console) - EF Core description: Reference guide for the Entity Framework Core Visual Studio Package Manager Console author: bricelam -ms.date: 10/13/2020 +ms.date: 10/27/2020 uid: core/miscellaneous/cli/powershell --- # Entity Framework Core tools reference - Package Manager Console in Visual Studio @@ -84,7 +84,13 @@ Why is a dummy project required? As mentioned earlier, the tools have to execute ### ASP.NET Core environment -To specify the environment for ASP.NET Core projects, set **env:ASPNETCORE_ENVIRONMENT** before running commands. +To specify [the environment](/aspnet/core/fundamentals/environments) for ASP.NET Core projects, set **env:ASPNETCORE_ENVIRONMENT** before running commands. + +Starting in EF Core 5.0, additional arguments can also be passed into Program.CreateHostBuilder allowing you to specify the environment on the command-line: + +```powershell +Update-Database -Args '--environment Production' +``` ## Common parameters @@ -197,6 +203,12 @@ Example that scaffolds only selected tables and creates the context in a separat Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Tables "Blog","Post" -ContextDir Context -Context BlogContext -ContextNamespace New.Namespace ``` +The following example reads the connection string from the project's configuration possibly set using the [Secret Manager tool](/aspnet/core/security/app-secrets#secret-manager). + +```dotnetcli +dotnet ef dbcontext scaffold Name=ConnectionStrings.Blogging Microsoft.EntityFrameworkCore.SqlServer +``` + ## Script-DbContext Generates a SQL script from the DbContext. Bypasses any migrations. Added in EF Core 3.0. @@ -228,16 +240,16 @@ The [common parameters](#common-parameters) are listed above. > [!TIP] > The To, From, and Output parameters support tab-expansion. -The following example creates a script for the InitialCreate migration, using the migration name. +The following example creates a script for the InitialCreate migration (from a database without any migrations), using the migration name. ```powershell -Script-Migration -To InitialCreate +Script-Migration 0 InitialCreate ``` The following example creates a script for all migrations after the InitialCreate migration, using the migration ID. ```powershell -Script-Migration -From 20180904195021_InitialCreate +Script-Migration 20180904195021_InitialCreate ``` ## Update-Database @@ -257,14 +269,14 @@ The [common parameters](#common-parameters) are listed above. The following example reverts all migrations. ```powershell -Update-Database -Migration 0 +Update-Database 0 ``` The following examples update the database to a specified migration. The first uses the migration name and the second uses the migration ID and a specified connection: ```powershell -Update-Database -Migration InitialCreate -Update-Database -Migration 20180904195021_InitialCreate -Connection your_connection_string +Update-Database InitialCreate +Update-Database 20180904195021_InitialCreate -Connection your_connection_string ``` ## Additional resources diff --git a/samples/core/Schemas/Migrations/CustomOperation.cs b/samples/core/Schemas/Migrations/CustomOperation.cs new file mode 100644 index 0000000000..d85b9a4b51 --- /dev/null +++ b/samples/core/Schemas/Migrations/CustomOperation.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; + +#region snippet_CreateUserOperation +class CreateUserOperation : MigrationOperation +{ + public string Name { get; set; } + public string Password { get; set; } +} +#endregion + +static class MigrationBuilderExtensions +{ + #region snippet_MigrationBuilderExtension + static OperationBuilder CreateUser( + this MigrationBuilder migrationBuilder, + string name, + string password) + { + var operation = new CreateUserOperation + { + Name = name, + Password = password + }; + migrationBuilder.Operations.Add(operation); + + return new OperationBuilder(operation); + } + #endregion +} + +#region snippet_MigrationsSqlGenerator +class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator +{ + public MyMigrationsSqlGenerator( + MigrationsSqlGeneratorDependencies dependencies, + IMigrationsAnnotationProvider migrationsAnnotations) + : base(dependencies, migrationsAnnotations) + { + } + + protected override void Generate( + MigrationOperation operation, + IModel model, + MigrationCommandListBuilder builder) + { + if (operation is CreateUserOperation createUserOperation) + { + Generate(createUserOperation, builder); + } + else + { + base.Generate(operation, model, builder); + } + } + + private void Generate( + CreateUserOperation operation, + MigrationCommandListBuilder builder) + { + var sqlHelper = Dependencies.SqlGenerationHelper; + var stringMapping = Dependencies.TypeMappingSource.FindMapping(typeof(string)); + + builder + .Append("CREATE USER ") + .Append(sqlHelper.DelimitIdentifier(operation.Name)) + .Append(" WITH PASSWORD = ") + .Append(stringMapping.GenerateSqlLiteral(operation.Password)) + .AppendLine(sqlHelper.StatementTerminator) + .EndCommand(); + } +} +#endregion + +class CustomOperationContext : DbContext +{ + readonly string _connectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Sample"; + + #region snippet_OnConfiguring + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .UseSqlServer(_connectionString) + .ReplaceService(); + #endregion +} \ No newline at end of file diff --git a/samples/core/Schemas/Migrations/CustomOperationMultiSql.cs b/samples/core/Schemas/Migrations/CustomOperationMultiSql.cs new file mode 100644 index 0000000000..d57e274378 --- /dev/null +++ b/samples/core/Schemas/Migrations/CustomOperationMultiSql.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; + +static class MultiSqlMigrationBuilderExtensions +{ + #region snippet_CustomOperationMultiSql + static OperationBuilder CreateUser( + this MigrationBuilder migrationBuilder, + string name, + string password) + { + switch (migrationBuilder.ActiveProvider) + { + case "Npgsql.EntityFrameworkCore.PostgreSQL": + return migrationBuilder + .Sql($"CREATE USER {name} WITH PASSWORD '{password}';"); + + case "Microsoft.EntityFrameworkCore.SqlServer": + return migrationBuilder + .Sql($"CREATE USER {name} WITH PASSWORD = '{password}';"); + } + + throw new Exception("Unexpected provider."); + } + #endregion +} diff --git a/samples/core/Schemas/Migrations/CustomOperationSql.cs b/samples/core/Schemas/Migrations/CustomOperationSql.cs new file mode 100644 index 0000000000..ab0d3cca83 --- /dev/null +++ b/samples/core/Schemas/Migrations/CustomOperationSql.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; + +static class SqlMigrationBuilderExtensions +{ + #region snippet_CustomOperationSql + static OperationBuilder CreateUser( + this MigrationBuilder migrationBuilder, + string name, + string password) + => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';"); + #endregion +} \ No newline at end of file