Skip to main content

Customize your EntityFramework Core Migrations

Why Customize?

If you have used migrations in EF6, and you remember how the dbo.__MigrationHistory table looked like, you might recall it had a column for ContextKey. This contained the complete namespace of the DbContext, to which a migration belonged. This was important because, there is a slight possibility that developers working on multiple DbContexts can accidentally give same name to their migrations. I do that when I start. All my DbContexts have the first migration called Init. I had no issues because these migration files existed in their own folder structures. Also when applied to the database, although the migrations had same names (ignoring the timestamps), they still had a ContextKey associated in the MigrationHistory table.


Enter EF Core, things changed. The column ContextKey was dropped. So my migrations look all the same.(again ignoring the timestamp). This pissed me off. I wanted complete control over how my migrations look like, be it as database entries, or files.


So I dug up the EF core libraries. Surprisingly enough, there is good design time support in EF Core for people like me who are not pleased enough with defaults.


How to Customize Your Migrations?


First we need custom implementations of 2 Types.


1. MigrationsIdGenerator- This interface is responsible for generating the Migration Id or the filename for the migrations

2. MigrationsScaffolder - This class (not interface yet, wait for EF Core 2.1) holds the DbContext against which the migration is to be created. This is the one that does all the heavy lifting.

Implement IMigrationsIdGenerator 
Here we just add a Prefix property, which can be appended to the 'name' which was originally typed through CLI to generate a new migration.
public sealed class CustomMigrationsIdGenerator : MigrationsIdGenerator
    {
        public string Prefix { get; set; }

        public override string GenerateId(string name)
        {
            return base.GenerateId(Prefix +"_" + name);
        }
    }

Next, we need a way to populate the Prefix property above. To do that we implement a custom class inheriting from MigrationsScaffolder.

Now to get a reference to the DbContext that is in question, just create a constructor that can call the base constructor and we are good! Within the contructor, using the dependencies, we get a reference to the MigrationsIdGenerator instance.


Next, we cast it to our custom implementation called CustomMigrationsIdGenerator.


Now, all we need to do is basically check which migration is running and choose a prefix we want based on that. I am using a switch case here to do this.



public sealed class MyMigrationsScaffolder : MigrationsScaffolder
    {
        public MyMigrationsScaffolder(MigrationsScaffolderDependencies dependencies) : base(dependencies)
        {
            CustomMigrationsIdGenerator idGenerator =(CustomMigrationsIdGenerator) dependencies.MigrationsIdGenerator;

            switch (dependencies.CurrentDbContext.Context.GetType().Name)
            {
                case "AbcDbContext":
                    idGenerator.Prefix = MigrationPrefix.Abc.ToString();
                    break;
                case "DefDbContext":
                    idGenerator.Prefix = MigrationPrefix.Def.ToString();
                    break;
                case "GhiDbContext":
                    idGenerator.Prefix = MigrationPrefix.Ghi.ToString();
                    break;
            }
        }
    }

    public enum MigrationPrefix
    {
        Abc,
        Def,
        Ghi
    }


Now our custom implementations must be wired up so they actually get utilized at design time. To do that, we need to add a custom Implementation of IDesignTimeServices. This class should sit in your startup project. You might try to act smart moving it to a library, thinking its just a class, and can be referenced from anywhere. Surprise! It doesn't work that way. So, put this is the startup project (console or webapi or mvc) that you have.


 public sealed class MyDesignTimeServices : IDesignTimeServices
    {
        public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
        {
            serviceCollection.AddSingleton<IMigrationsIdGenerator, CustomMigrationsIdGenerator>();
            serviceCollection.AddSingleton<MigrationsScaffolder, MyMigrationsScaffolder>();
        }
    }
If you did everything correctly, on executing:
dotnet ef migrations add mymigration --context mydbcontext

you should see that your migration file has the Prefix that you want. Naturally when you apply the migration to the database, the MigrationId will have your prefix as well.

Hope this helps.


Comments

Popular posts from this blog

Invoking the thought process

Thoughts, ideas and dreams.Things without which it is a life as worthless as anything. Our deeds are a clear reflection of what we think - be it any matter for the sake of discussion. It is really a neccessity to be thoughtfully active which is a scale of our mind activity. The more we think, the more we develop a view about something. And this determines the amount of understanding we have developed about things of concern. Dreams are something which we all know by the phrase- unfulfilled desires. So naturally, thinking big and intelligent makes you dream of big and great goals. Everyone has got a beautiful mind which when best used definintely can result in wonderful thoughts, ideas and dreams. Thats some food for thought, think about it.