add-migration command fails on ApplicationDbContex

912 views Asked by At

App: Asp.NET Core 1.1.1, EF Core

Platform: Visual Studio 2017 5.3.3

Authentication Mode: Individual User Accounts

Following this tutorial from ASP.NET official team, the following command runs successfully:

PM> Add-Migration MyFirstMigration -context BloggingContext

As we know VS2017 by default creates ApplicationDbContext under MyProject\Data folder for creating user tables (ASPNETUsers, ASPNETRoles etc...) for authentication. But for that when I run the following command it first gives me Error-1 below. And when I follow the instructions in first error message, I get Error-2 below. Question: How do I make the following command work without using IDbContextFactory?

PM> Add-Migration MyFirstAuthenMigration -context ApplicationDbContext

Error 1

No parameterless constructor was found on 'ApplicationDbContext'. Either add a parameterless constructor to 'ApplicationDbContext' or add an implementation of 'IDbContextFactory' in the same assembly as 'ApplicationDbContext'.

After I add a parameterless constructor in ApplicationDbContext.cs (shown below) I get the second error shown below:

Error 2

No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.

ApplicationDbContext.cs

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    //NOTE: I added following constructor after Error 1 shown above
    public ApplicationDbContext()
    {
    }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}
1

There are 1 answers

0
user743414 On

You can't get it to work. Because EF needs to know which connectionstring should be used. You can either put the connectionstring into your OnModelCreating method. Which is bad when you have more than one DB (dev, test and production for example). Or you implement IDbContextFactory which will be loaded by EF through reflection.

So implementing IDbContextFactory is the better option. Simply create it where you have your ApplicationDbContext. Sample implementation.

public class DomainContextFactory : IDbContextFactory<DomainContext>
{
    public string BasePath { get; protected set; }

    public DomainContext Create()
    {
        var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        var basePath = AppContext.BaseDirectory;
        return Create(basePath, environmentName);
    }

    public DomainContext Create(DbContextFactoryOptions options)
        => Create(options.ContentRootPath, options.EnvironmentName);

    private DomainContext Create(string basePath, string environmentName)
    {
        BasePath = basePath;
        var configuration = Configuration(basePath, environmentName);
        var connectionString = ConnectionString(configuration.Build());
        return Create(connectionString);
    }

    private DomainContext Create(string connectionString)
    {
        if (string.IsNullOrEmpty(connectionString))
        {
            throw new ArgumentException($"{nameof(connectionString)} is null or empty", nameof(connectionString));
        }
        var optionsBuilder = new DbContextOptionsBuilder<DomainContext>();
        return Configure(connectionString, optionsBuilder);
    }

    protected virtual IConfigurationBuilder Configuration(string basePath, string environmentName)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("constr.json")
            .AddJsonFile($"constr.{environmentName}.json", true)
            .AddEnvironmentVariables();
        return builder;
    }

    protected virtual string ConnectionString(IConfigurationRoot configuration)
    {
        string connectionString = configuration["ConnectionStrings:DefaultConnection"];
        return connectionString;
    }

    protected virtual DomainContext Configure(string connectionString, DbContextOptionsBuilder<DomainContext> builder)
    {
        builder.UseSqlServer(connectionString, opt => opt.UseRowNumberForPaging());
        DomainContext db = new DomainContext(builder.Options);
        return db;
    }

    DomainContext IDbContextFactory<DomainContext>.Create(DbContextFactoryOptions options)
        => Create(options.ContentRootPath, options.EnvironmentName);
}

It works with 3 configuration files (final, local and test)

  • constr.dev-final.json
  • constr.dev-local.json
  • constr.dev-test.json

The usage looks like the following:

public override IServiceResult<IList<Rolle>> LoadAllData()
{
    using (var db = this.DomainContextFactory.Create())
    {
        Task<List<Rolle>> rollen = db.Roles
            .ToListAsync<Rolle>();
        return new ServiceResult<IList<Rolle>>(rollen.Result, rollen.Result.Count);

    }
}

And using "Add-Migration" inside MSVC works also.