entity-frameworkentity-framework-core

EF Core: Add custom LINQ in Up method of Migration class


I would like to execute some LINQ in Up method of migrations. Problem is I don't know how can I get DbContext instance?

This is code generated by migrations add:

public partial class MyTableAddFieldTitle : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<string>(
            name: "Title",
            table: "MyTable",
            nullable: true);
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "Title",
            table: "MyTable");
    }
}

I would like to add something like that in Up method:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AddColumn<string>(
        name: "Title",
        table: "MyTable",
        nullable: true);

    var context = ?????;

    //Actual code is much more complicated, but the principle is the same.
    foreach (var item in context.Set<DbMyTable>())
      item.Title = item.SomeStringColumn;
    context.SaveChanges();
}

Problem is how to get context instance? I tried with DI in constructor:

protected MyTableAddFieldTitle(MyContext context)
{
}

but I get error:

MissingMethodException: No parameterless constructor defined for this object. System.RuntimeTypeHandle.CreateInstance(RuntimeType type, bool publicOnly, ref bool canBeCached, ref RuntimeMethodHandleInternal ctor)


Solution

  • I found solution.

    In Startup class I defined static variable:

    public static Func<MyContext> ContextFactory;
    

    In constructor of Startup class I assigned variable:

    public Startup(IHostingEnvironment env, IConfiguration config)
    {
        MyContext GetContext(IConfiguration configuration, IHostingEnvironment environment)
        {
            var builder = new DbContextOptionsBuilder<MyContext>();
            builder.UseSqlServer(configuration["ConnectionStrings:Web"], b => b.MigrationsAssembly("Web.Hosting"));
            if (environment.IsDevelopment())
                builder.EnableSensitiveDataLogging();
            return new MyContext(builder.Options);
        }
    
        ContextFactory = () => GetContext(config, env);
    }
    

    Then in Migrations I simply call ContextFactory:

    var context = Startup.ContextFactory();
    context.Set<DbMyTable>().Where(....
    

    To avoid error field does not exists I create 2 migration files (dotnet ef migrations add).
    First adds field:

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<string>(
            name: "Title",
            table: "MyTable",
            nullable: true);
    }
    

    And second (empty) executes query:

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        var context = Startup.ContextFactory();
        context.Set<DbMyTable>().Where(....
    }