18

I tried to inject IConfiguration into the migration (in constructor), and got exception: "No parameterless constructor defined for this object."

any workaround?

arielorvits
  • 5,235
  • 8
  • 36
  • 61
  • Could you describe more about what you're trying to accomplish by injecting a service? – bricelam Mar 07 '17 at 16:43
  • @bricelam, tried to write migration that insert data into db, this data is depending on data in other db (which not mapped to entity-framework). so the migration should get from configuration the second db connection-string (since this is not the same for dev/staging/prod envs). we eventually run the script manually on all envs. – arielorvits Mar 07 '17 at 19:35

3 Answers3

15

you cannot, the migrations need to be able to run outside the context of your application.

Since the Entity-framework command-line tool analyzes your code but does not run the startup.cs class.

Also it is not advisable. your migrations should be plain simple and not depend on anything. if it would, it could lead to major runtime side-effects where missing config could lead to missing tables or columns in production.

additional advise

If it involves a lot of small/equal/manual changes. Best way is to generate your migration file. Why? This way your migration will be deterministic: you know what the outcome will be. If a line in your migration fails, it is simple and clear why that is and easily(er) fixable.

Joel Harkes
  • 10,975
  • 3
  • 46
  • 65
  • No, it runs code. You can insert `throw Exception("Message");` into your Configuration, then execute Add-Migration and you will see "Message" in console. But migration will created. – iRumba Feb 02 '20 at 08:20
  • 3
    "the migrations need to be able to run outside the context of your application". False (at least in the current version of EF). The EF tools use your programs host builder to discover all services & config required to create and execute migrations. – Jeremy Lakeman Nov 12 '20 at 04:29
  • @jeremyLakeman thanks for the update. Does it also run at startup with same environment variables and config as the application? – Joel Harkes Nov 12 '20 at 20:16
  • Environment variables are ... from the environment. AFAIK, the host is built, but not started. Which should include config. Then the service provider is used to discover the EF services required. – Jeremy Lakeman Nov 12 '20 at 23:30
  • Alright thanks for the update. I don't really work on dotnet anymore so that's why I ask. you may suggest a change to my answer :) – Joel Harkes Nov 14 '20 at 09:02
13

There's a way to do what you want to do. In my scenario, I would like to use the database name in the connection string through the DbContext. EF core 2.1.1 is used. The code is modified from here

Create a custom MigrationsAssembly service

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using System;
using System.Reflection;

public class ContextAwareMigrationsAssembly : MigrationsAssembly
{
    private readonly DbContext context;

    public ContextAwareMigrationsAssembly(
        ICurrentDbContext currentContext,
        IDbContextOptions options,
        IMigrationsIdGenerator idGenerator,
        IDiagnosticsLogger<DbLoggerCategory.Migrations> logger) : base(currentContext, options, idGenerator, logger)
    {
        context = currentContext.Context;
    }

    /// <summary>
    /// Modified from http://weblogs.thinktecture.com/pawel/2018/06/entity-framework-core-changing-db-migration-schema-at-runtime.html
    /// </summary>
    /// <param name="migrationClass"></param>
    /// <param name="activeProvider"></param>
    /// <returns></returns>
    public override Migration CreateMigration(TypeInfo migrationClass, string activeProvider)
    {
        var hasCtorWithDbContext = migrationClass
                .GetConstructor(new[] { typeof(DbContext) }) != null;

        if (hasCtorWithDbContext)
        {
              var instance = (Migration)Activator.CreateInstance(migrationClass.AsType(), context);
              instance.ActiveProvider = activeProvider;
              return instance;
        }

        return base.CreateMigration(migrationClass, activeProvider);
    }
}

Replace the IMigrationAssembly service in your DbContext with your custom class

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.ReplaceService<IMigrationsAssembly, ContextAwareMigrationsAssembly>();
}

Then you can add a DbContext parameter in your migration.

public Migration20180801(DbContext context)
{
    DatabaseName = context.Database.GetDbConnection().Database;
}

In your case, you can replace all the DbContext references with IConfiguration and the relevant instance in the CreateMigration override.

Marc Lopez
  • 561
  • 8
  • 15
  • If using DbContext pooling, exception will be thrown if using OnConfiguring to modify DbContextOptions. You can use dbContextOptionsBuilder.ReplaceService() instead, assuming you're not changing EF internal service provider. – Vinh C Jul 11 '19 at 02:31
  • 1
    has anyone actually tried this with IConfiguration? I tried to inject IConfiguration into a class like ContextAwareMigrationsAssembly but it isn't resolved. – Diogo Nov 08 '21 at 14:17
2

If it is just about your connection-string (is it?), you may want to check this answer, which basically suggests this code in your startup-project (not in your migrations-project):

var myConnectionString = Configuration.GetConnectionString(myConnectionStringName);
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(
    myConnectionString ,
    x => x.MigrationsAssembly(myDbContextAssemblyName)));
Yahoo Serious
  • 3,728
  • 1
  • 33
  • 37