Entity Framework Core utilizes a multitude of injected services under the hood to handle Migrations.
There does not appear to be any "surface level" configuration exposed to apply logic that specifies Migration Folder's for created Migrations as a default, in the C# packages.
I know that you can specify the output folder via the -o flag on Add-Migration, but that involves a manual process every single time.
I am curious to know if anyone has managed to find the specific service that actually resolves the folder for where Migrations will go when you invoke Add-Migration, and has managed to figure out a way to override it's behavior to add in custom logic for making the output folder more specific.
In my case, I want to output my Migrations to the path of <MyProject>/Data/Migrations/Year/Month/, as anyone who has maintained a much longer running project can attest to, the default of dumping all migrations into a single folder can eventually become deeply unwieldy as the list grows incredibly long (and worst of all, the Migrations you care the most about tend to be at the bottom of that list)
I have seen projects that have had several hundred migration .cs files in the same folder, which is very frustrating to sift through in one directory completely unorganized.
Being able to automate the process of Migration files being output to a form of /year/month/ architecture would be quite helpful.
Note: I acknowledge this can likely be achieved via some kind of Powershell cmdlet or whatnot, but it will require the developer to have additional onboarding knowledge share and they will need to know the specific command to run.
Whereas if the logic is handled in a "it just works" form via C# overrides of services, no additional knowledge sharing will be required and another step of on ramping will be removed.
Anyone who has had to work with a legacy codebase where the original dev left 8 years ago certainly can understand the desire for such automation to be "baked" into the core code of the application, rather than relying on an external script/cmdlet that they have to go and find.
Attempt 1: Implementing a custom IMigrationsScaffolder
I installed the Nuget pack Microsoft.EntityFrameworkCore.Design which, according to the docs should have the interface IMigrationsScaffolder
It looks like the main entry point for scaffolding and removal of migrations, as per the docs here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.migrations.design.imigrationsscaffolder?view=efcore-7.0
However, even after installing the package it does not appear to actually include that interface in the assembly, any attempt to locate it inside of the namespace (or anything below that) Microsoft.EntityFrameworkCore.Migrations.Design fails, Visual Studio doesn't recognize the interface, yet "search for this type on nuget" continues to point to that nuget package anyways.
Nuget Package explore for v7.0.12 seems to indicate the file is present and declared in the Assembly: https://nuget.info/packages/Microsoft.EntityFrameworkCore.Design/7.0.12
And yet if I even reference the namespace Microsoft.EntityFrameworkCore.Migrations.Design Visual Studio complains that it does not recognize that namespace (specifically the .Design portion)
This is very strange, I havent seen this sort of behavior before where a section of the Nuget package just seems to not actually exist according to Visual Studio.
Edit: After the recommendation from Svyatoslav Danyliv below I was able to reference the interface.
I started off by creating the following, by duplicating how Microsoft's code works and utilizing Decorator Pattern to inject my own basic logic (which should just log to console when invoked):
public class TestMigrationsScaffolder : IMigrationsScaffolder
{
private IMigrationsScaffolder Component { get; }
private MigrationsScaffolderDependencies Dependencies { get; }
public TestMigrationsScaffolder(MigrationsScaffolderDependencies dependencies)
{
Component = new MigrationsScaffolder(dependencies);
Dependencies = dependencies;
}
public ScaffoldedMigration ScaffoldMigration(
string migrationName,
string? rootNamespace,
string? subNamespace = null,
string? language = null
)
{
return Component.ScaffoldMigration(
migrationName,
rootNamespace,
subNamespace,
language
);
}
public MigrationFiles RemoveMigration(
string projectDir,
string? rootNamespace,
bool force,
string? language
)
{
Dependencies.OperationReporter
.WriteInformation($"Project Dir: {projectDir}");
return Component.RemoveMigration(
projectDir,
rootNamespace,
force,
language
);
}
public MigrationFiles Save(
string projectDir,
ScaffoldedMigration migration,
string? outputDir
)
{
Dependencies.OperationReporter
.WriteInformation($"Project Dir: {projectDir}");
Dependencies.OperationReporter
.WriteInformation($"Output Dir: {outputDir}");
return Component.Save(projectDir, migration, outputDir);
}
}
I then attempted to inject this service to override functionality inside of the DbContext configuration step, inside of Program.cs:
builder.Services.AddDbContext<ExampleDataContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("ExampleDatabase"));
options.ReplaceService<IMigrationsScaffolder, TestMigrationsScaffolder>();
});
When I ran Add-Migration Test in the console, it scaffolded up and created the migration as usual, but did not perform any of my new logging to the console as expected.
I also tried the same as above but with Console.WriteLine(...) and that did not succeed either.
That is as far as I was able to get so far, if anyone know the "right" way to override the services that are called by Remove-Migration and Add-Migration and etc, please let me know!