-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Add runtime migration creation and application #37415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
633165e
ed69364
872ffdc
2609bea
52999ad
62c8e9e
4a84f33
31ded81
9164772
84eec3f
4316282
8e7cad7
50dde25
234868e
b78e506
848777b
9303834
953c86c
9c8d771
b6a523b
6f7ae79
f17dddc
ac1deb1
6b0a93d
ad289d2
13f5377
3b99d66
4c48e09
f0163c7
01bfe2f
355c7b7
6e5e59f
cd324fd
5bf8a0b
8129933
e21555c
e9b3d54
a5e5331
e7b6329
72a3c0f
a5b2659
9a62a7b
1d82f83
6d64bb1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using Microsoft.EntityFrameworkCore.Internal; | ||
| using Microsoft.EntityFrameworkCore.Migrations.Design; | ||
| using Microsoft.EntityFrameworkCore.Migrations.Design.Internal; | ||
|
|
||
| namespace Microsoft.EntityFrameworkCore.Design.Internal; | ||
|
|
||
|
|
@@ -70,31 +73,8 @@ public virtual MigrationFiles AddMigration( | |
| string? @namespace, | ||
| bool dryRun) | ||
| { | ||
| var invalidPathChars = Path.GetInvalidFileNameChars(); | ||
| if (name.Any(c => invalidPathChars.Contains(c))) | ||
| { | ||
| throw new OperationException( | ||
| DesignStrings.BadMigrationName(name, string.Join("','", invalidPathChars))); | ||
| } | ||
|
|
||
| if (outputDir != null) | ||
| { | ||
| outputDir = Path.GetFullPath(Path.Combine(_projectDir, outputDir)); | ||
| } | ||
|
|
||
| var subNamespace = SubnamespaceFromOutputPath(outputDir); | ||
|
|
||
| using var context = _contextOperations.CreateContext(contextType); | ||
| var contextClassName = context.GetType().Name; | ||
| if (string.Equals(name, contextClassName, StringComparison.Ordinal)) | ||
| { | ||
| throw new OperationException( | ||
| DesignStrings.ConflictingContextAndMigrationName(name)); | ||
| } | ||
|
|
||
| var services = _servicesBuilder.Build(context); | ||
| EnsureServices(services); | ||
| EnsureMigrationsAssembly(services); | ||
| var (resolvedOutputDir, subNamespace, services) = PrepareForMigration(name, outputDir, context); | ||
|
|
||
| using var scope = services.CreateScope(); | ||
| var scaffolder = scope.ServiceProvider.GetRequiredService<IMigrationsScaffolder>(); | ||
|
|
@@ -103,7 +83,7 @@ public virtual MigrationFiles AddMigration( | |
| // TODO: Honor _nullable (issue #18950) | ||
| ? scaffolder.ScaffoldMigration(name, _rootNamespace ?? string.Empty, subNamespace, _language, dryRun) | ||
| : scaffolder.ScaffoldMigration(name, null, @namespace, _language, dryRun); | ||
| return scaffolder.Save(_projectDir, migration, outputDir, dryRun); | ||
| return scaffolder.Save(_projectDir, migration, resolvedOutputDir, dryRun); | ||
| } | ||
|
|
||
| // if outputDir is a subfolder of projectDir, then use each subfolder as a sub-namespace | ||
|
|
@@ -272,6 +252,111 @@ public virtual void HasPendingModelChanges(string? contextType) | |
| _reporter.WriteInformation(DesignStrings.NoPendingModelChanges); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
| /// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
| /// 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. | ||
| /// </summary> | ||
| [RequiresDynamicCode("Runtime migration compilation requires dynamic code generation.")] | ||
| public virtual MigrationFiles AddAndApplyMigration( | ||
| string name, | ||
| string? outputDir, | ||
| string? contextType, | ||
| string? @namespace, | ||
| string? connectionString) | ||
| { | ||
| using var context = _contextOperations.CreateContext(contextType); | ||
| var (resolvedOutputDir, subNamespace, services) = PrepareForMigration(name, outputDir, context); | ||
|
|
||
| if (connectionString != null) | ||
| { | ||
| context.Database.SetConnectionString(connectionString); | ||
| } | ||
|
|
||
| using var scope = services.CreateScope(); | ||
| var migrator = scope.ServiceProvider.GetRequiredService<IMigrator>(); | ||
| var scaffolder = scope.ServiceProvider.GetRequiredService<IMigrationsScaffolder>(); | ||
| var compiler = scope.ServiceProvider.GetRequiredService<IMigrationCompiler>(); | ||
| var migrationsAssembly = scope.ServiceProvider.GetRequiredService<IMigrationsAssembly>(); | ||
|
|
||
| if (!migrator.HasPendingModelChanges()) | ||
| { | ||
| _reporter.WriteInformation(DesignStrings.NoPendingModelChanges); | ||
| migrator.Migrate(null); | ||
| _reporter.WriteInformation(DesignStrings.Done); | ||
| // Return empty MigrationFiles to indicate no migration was created. | ||
| // When serialized to JSON (with --json), all file path properties will be null. | ||
| return new MigrationFiles(); | ||
| } | ||
|
|
||
| _reporter.WriteInformation(DesignStrings.CreatingAndApplyingMigration(name)); | ||
|
|
||
| var migration = | ||
| string.IsNullOrEmpty(@namespace) | ||
| ? scaffolder.ScaffoldMigration(name, _rootNamespace ?? string.Empty, subNamespace, _language, dryRun: true) | ||
| : scaffolder.ScaffoldMigration(name, null, @namespace, _language, dryRun: true); | ||
|
|
||
| MigrationFiles? files = null; | ||
| try | ||
| { | ||
| files = scaffolder.Save(_projectDir, migration, resolvedOutputDir, dryRun: false); | ||
|
|
||
| var compiledAssembly = compiler.CompileMigration(migration, context.GetType()); | ||
| migrationsAssembly.AddMigrations(compiledAssembly); | ||
|
|
||
| migrator.Migrate(migration.MigrationId); | ||
|
|
||
| _reporter.WriteInformation(DesignStrings.MigrationCreatedAndApplied(migration.MigrationId)); | ||
|
|
||
| return files; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| throw new OperationException( | ||
| DesignStrings.AddAndApplyMigrationFailed(name, ex.Message), ex); | ||
| } | ||
| } | ||
|
Comment on lines
+300
to
+319
|
||
|
|
||
| /// <summary> | ||
| /// Prepares common resources for migration operations. | ||
| /// </summary> | ||
| private (string? outputDir, string? subNamespace, IServiceProvider services) | ||
| PrepareForMigration(string name, string? outputDir, DbContext context) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(name)) | ||
| { | ||
| throw new OperationException(DesignStrings.MigrationNameRequired); | ||
| } | ||
|
Comment on lines
+327
to
+330
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, then remove this check since it's already checked before this call |
||
|
|
||
| var invalidPathChars = Path.GetInvalidFileNameChars(); | ||
| if (name.Any(c => invalidPathChars.Contains(c))) | ||
| { | ||
| throw new OperationException( | ||
| DesignStrings.BadMigrationName(name, string.Join("','", invalidPathChars))); | ||
| } | ||
|
|
||
| if (outputDir != null) | ||
| { | ||
| outputDir = Path.GetFullPath(Path.Combine(_projectDir, outputDir)); | ||
| } | ||
|
|
||
| var subNamespace = SubnamespaceFromOutputPath(outputDir); | ||
|
|
||
| var contextClassName = context.GetType().Name; | ||
| if (string.Equals(name, contextClassName, StringComparison.Ordinal)) | ||
| { | ||
| throw new OperationException( | ||
| DesignStrings.ConflictingContextAndMigrationName(name)); | ||
| } | ||
|
|
||
| var services = _servicesBuilder.Build(context); | ||
| EnsureServices(services); | ||
| EnsureMigrationsAssembly(services); | ||
|
|
||
| return (outputDir, subNamespace, services); | ||
| } | ||
|
|
||
| private static void EnsureServices(IServiceProvider services) | ||
| { | ||
| var migrator = services.GetService<IMigrator>(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there are no pending model changes, the method returns an empty
MigrationFilesobject (line 298). However, the calling code inDatabaseUpdateCommandwill attempt to output JSON from this empty object when--jsonis specified. This will result in null values for all file paths in the JSON output, which may be confusing to users. Consider returning null or a special indicator, or documenting this behavior clearly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit a5b2659 - added a comment documenting that an empty
MigrationFilesis returned when there are no pending changes, and that JSON serialization will produce null values for all file paths.