26

Is there way that dependency injection can be configured/bootstrapped when using Entity Framework's migration commands?

Entity Framework Core supports dependency injection for DbContext subclasses. This mechanism includes allowing for configuration of data access outside of of the DbContext.

For example, the following would configure EF to persist to a SQL server using a connection string retrieved from config.json

ServiceCollection services = ...

var configuration = new Configuration().AddJsonFile( "config.json" );
services.AddEntityFramework( configuration )
    .AddSqlServer()
    .AddDbContext<BillingDbContext>( config => config.UseSqlServer() );

However, the migrations commands do not know to execute this code so Add-Migration will fail for lack of a provider or lack of a connection string.

Migrations can be made to work by overriding OnConfiguring within the DbContext subclass to specify the provider and configuration string, but that gets in the way when different configuration is desired elsewhere. Ultimately keeping my the migration commands and my code both working becomes undesirably complex.

Note: My DbContext lives in a different assembly than the entry point that uses it and my solution has multiple start-up projects.

Søren
  • 6,517
  • 6
  • 43
  • 47
vossad01
  • 11,552
  • 8
  • 56
  • 109
  • Figuring this out is part of issue [#639](https://github.com/aspnet/EntityFramework/issues/639). In ASP.NET 5, we'll call `Startup.ConfigureServices()`. In your opinion, would it be good to use the same convention for non-ASP.NET 5 projects? – bricelam Mar 18 '15 at 20:57
  • @bricelam Now that I have done some development in ASP.NET 5 (was using EF7 in traditional .NET application) I can say standardizing around the `Startup` class sounds good to me. – vossad01 Jun 28 '15 at 17:41
  • I pushed for it in [aspnet/Hosting#286](https://github.com/aspnet/Hosting/issues/286), but lost. The decision was to have different DbContext loaders for different application types. See [aspnet/EntityFramework#2357](https://github.com/aspnet/EntityFramework/issues/2357). – bricelam Jun 29 '15 at 16:08

8 Answers8

25

If you are looking for a solution to configure context for migrations, you can use this in your DBContext class:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();
            var connectionString = configuration.GetConnectionString("DbCoreConnectionString");
            optionsBuilder.UseSqlServer(connectionString);
        }
    }

Remember to install those two packages to have SetBasePath and AddJsonFile methods: Microsoft.Extensions.Configuration.FileExtensions

Microsoft.Extensions.Configuration.Json

Mik
  • 3,998
  • 2
  • 26
  • 16
7

Use IDesignTimeDbContextFactory

If a class implementing this interface is found in either the same project as the derived DbContext or in the application's startup project, the tools bypass the other ways of creating the DbContext and use the design-time factory instead.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace MyProject
{
    public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
    {
        public BloggingContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
            optionsBuilder.UseSqlite("Data Source=blog.db");

            return new BloggingContext(optionsBuilder.Options);
        }
    }
}

applied in Entity Framework 2.0, 2.1


Using IDbContextFactory<TContext> is now obsolete.

Implement this interface to enable design-time services for context types that do not have a public default constructor. Design-time services will automatically discover implementations of this interface that are in the same assembly as the derived context.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace MyProject
{
    public class BloggingContextFactory : IDbContextFactory<BloggingContext>
    {
        public BloggingContext Create()
        {
            var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
            optionsBuilder.UseSqlServer("connection_string");

            return new BloggingContext(optionsBuilder.Options);
        }
    }
}

more info : https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext

If you're not happy with the hard-coded connection-string, take a look at this article.

Community
  • 1
  • 1
Søren
  • 6,517
  • 6
  • 43
  • 47
  • 1
    This class is obsolete now. – Shimmy Weitzhandler May 29 '19 at 07:16
  • Your updated solution is exactly the same as @Anton's. It should be reverted to its older state and kept for historical purposes. – Daniel Sep 28 '20 at 18:56
  • 1
    Note that `IDbContextFactory<>` was moved to `Microsoft.EntityFrameworkCore` namespace in efcore 5 and is no longer obsolete: https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1?view=efcore-5.0 – ghord Jan 15 '22 at 20:30
6

As @bricelam commented this functionality does not yet exist in Entity Framework 7. This missing functionality is tracked by GitHub issue aspnet/EntityFramework#639

In the mean time, the easier workaround I found was to utilize a global state rather than hassle with subclassing. Not usually my first design choice but it works well for now.

In MyDbContext:

public static bool isMigration = true;

protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder )
{
    // TODO: This is messy, but needed for migrations.
    // See https://github.com/aspnet/EntityFramework/issues/639
    if ( isMigration )
    {
        optionsBuilder.UseSqlServer( "<Your Connection String Here>" );
    }
}

In Startup.ConfigureServices().

public IServiceProvider ConfigureServices( IServiceCollection services )
{
    MyContext.isMigration = false;

    var configuration = new Configuration().AddJsonFile( "config.json" );
    services.AddEntityFramework( configuration )
        .AddSqlServer()
        .AddDbContext<MyDbContext>( config => config.UseSqlServer() );
    // ...
}

(The configuration code actually lives in an Autofac Module in my case.)

Community
  • 1
  • 1
vossad01
  • 11,552
  • 8
  • 56
  • 109
4

In .NET Core since version 2.1 should be used IDesignTimeDbContextFactory because IDbContextFactory is obsolete.

public class FooDbContextFactory : IDesignTimeDbContextFactory<FooDbContext>
{
    public FooDbContext CreateDbContext(string[] args)
    {
        IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

        var builder = new DbContextOptionsBuilder<FooDbContext>();
        var connectionString = configuration.GetConnectionString("ConnectionStringName");
        builder.UseSqlServer(connectionString);

        return new FooDbContext(builder.Options);
    }
}
Anton Kalcik
  • 2,107
  • 1
  • 25
  • 43
2

To combine the answers above this works for me

private readonly bool isMigration = false;
public MyContext()
{
    isMigration = true;
}

public MyContext(DbContextOptions<MyContext> options) : base(options)
{

}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (isMigration)
    {
        optionsBuilder.UseSqlServer("CONNECTION_STRING");
    }
}
Dživo Jelić
  • 1,723
  • 3
  • 25
  • 47
1

I know this is a old question but I use the onConfiguring method and I don't have this problem

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(Startup.Configuration.Get("Data:DefaultConnection:ConnectionString"));
}
Pedro Fillastre
  • 892
  • 6
  • 10
  • 2
    Thanks Pedro, unless something has changed, this does not work out in my situation. The `Startup` class my application uses cannot be referenced in the `DbContext` subclass because they live in different assemblies. Doing this configuration here and in `Startup` is not compatible. – vossad01 Jun 28 '15 at 18:01
1

If you want to use 'Migration' folder from another assembly in the same solution then you could use this:

serviceCollection.AddDbContext<VSContext>(options =>
        {
            options.UseSqlServer(_connectionString, x => x.MigrationsAssembly("VS.Infrastructure.DAL"));
        });

If helped please UpVote as I trying to earn points - Thank you

More detailed: https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/projects?tabs=dotnet-core-cli

0

I just ask for an instance and run migrations in my Startup.cs file

  public void ConfigureServices(IServiceCollection services)
    {
        // ASPNet Core Identity
        services.AddDbContext<RRIdentityDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("RRIdentityConnectionString")));

     }

And then in Configure:

   public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        var rrIdentityContext = app.ApplicationServices.GetService<RRIdentityDbContext>();
        rrIdentityContext.Database.Migrate();
    }

Note: There is no 'EnsureCreated' for the database. Migrate is supposed to create it if it doesn't exist, although how it is supposed to figure out the permissions I don't know - so I created an empty database.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • Thank you. In my scenario I have a `DbContext` with custom configuration in the `OnConfiguring` method, and I want it do generate multiple databases, so I need to call `context.Database.Migrate` post context configuration. Where is the right place to do that? – Shimmy Weitzhandler May 29 '19 at 10:25