0

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!

2
  • Facing the same issue. For me works only by calling serviceCollection.Replace() with a custom Scaffolder. But maybe you found better way Commented Apr 18, 2024 at 12:37
  • No I unfortunately never did, if you got it working please do post your solution, I'd love to see it! Commented Apr 23, 2024 at 22:28

1 Answer 1

1

You have to correct how Microsoft.EntityFrameworkCore.Design is referenced in your project IncludeAssets is important:

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12">
      <PrivateAssets>none</PrivateAssets>
      <IncludeAssets>runtime; compile; build; native; contentfiles; buildtransitive</IncludeAssets>
    </PackageReference>

  ...

  </ItemGroup>
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! This worked swimmingly towards getting my progress further, but then I hit a new blocker afterwards. I have updated my post above with more information regarding how far I got.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.