Efcore: IDesignTimeDbContextFactory Constructor with parameter

Created on 18 Aug 2017  路  12Comments  路  Source: dotnet/efcore

I implement IDesignTimeDbContextFactory for dotnet ef migrations add.
My current code is:

    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
    {
        public AppDbContext CreateDbContext(string[] args)
        {
            var builder = new DbContextOptionsBuilder<AppDbContext>();
            builder.UseSqlServer("Server=10.0.0.73\\MSSQLSERVER2016;Database=OpPISWebDevelopment;User Id=oppisweb;Password=oppisweb;Max Pool Size=10;Connection Timeout=100000;");

            return new AppDbContext(builder.Options);
        }
    }

But I don't want hard coded ConnectionString.
I tried like this:

    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
    {
        private IConfiguration config;
        public ApplicationDbContextFactory(IConfiguration config)
        {
            this.config = config;
        }
        public AppDbContext CreateDbContext(string[] args)
        {
            var builder = new DbContextOptionsBuilder<AppDbContext>();
            builder.UseSqlServer(this.config["ConnectionString"]);

            return new AppDbContext(builder.Options);
        }
    }

but I get error:

System.MissingMethodException: No parameterless constructor defined for this object.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContextFromFactory(Type factory)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.<>c__DisplayClass12_0.b__3()
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func1 factory) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType) at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.ctor>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_01.b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
No parameterless constructor defined for this object.

when executed
dotnet ef migrations add

How to get connection string from appsettings.json?

closed-question

Most helpful comment

I was finally able to figure this out. There were a few things that tripped me up:

To Start:

C:\Users\ben.rubinger\Desktop\EF Migrations Help\EF Migrations Help>dotnet ef migrations add new
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]

      User profile is available. Using 'C:\Users\ben.rubinger\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.0.1-rtm-125 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Your target project 'EF Migrations Help' doesn't match your migrations assembly 'Data'. Either change your target project or change your migrations assembly.
Change your migrations assembly by using DbContextOptionsBuilder. E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("EF Migrations Help")). By default, the migrations assembly is the assembly containing the DbContext.
Change your target project to the migrations project by using the Package Manager Console's Default project drop-down list, or by executing "dotnet ef" from the directory containing the migrations project.

C:\Users\ben.rubinger\Desktop\EF Migrations Help\EF Migrations Help>

From here, I moved into the data project directory:
C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new

No executable found matching command "dotnet-ef"

This was because I didn't have:

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
  </ItemGroup>

in the relevant CSProj.

After I did this:

C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new

Startup project 'Data.csproj' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the Entity Framework Core .NET Command Line Tools with this project, add an executable project targeting .NET Core or .NET Framework that references this project, and set it as the startup project using --startup-project; or, update this project to cross-target .NET Core or .NET Framework.

C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>

Note that my solution did not involve following this advice. Instead, I finally figured out that I had to set the startup project:
C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new --startup-project "..\EF Migrations Help"

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\ben.rubinger\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.0.1-rtm-125 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
The name 'new' is used by an existing migration.

and it finally worked! It seemed counterintuitive that I need to run the command from the data directory, and point it to the original webapp directory, yet this gave me success. I'm posting this here in case others hit this. I struggled a bit with the bits and pieces of documentation.

Update: while the above is true for the sample project, for my actual project I had a last hurdle. My data project was targeting .netcoreapp2.0, and I got an error stating "The specified framework 'Microsoft.NETCore.App', version '2.0' was not found". I had to switch to .netstandard2.0 to finally get it working. Quite a few hurdles. I didn't think this was a very strange scenario I was trying to achieve.

All 12 comments

@MaklaCof The factory is not, by design, resolved from the service provider. The way to get your configuration into the factory is to duplicate or refactor and re-use the code you have in Main that sets up your configuration. In fact, if you factor this out appropriately into a BuildWebHost method that is just about configuration and not about running the application, then you should not need to have a factory at all. For more information, see: https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/

@ajcvickers Is it your expectation that the above suggestion (not needing an explicit factory) would still apply in a scenario where you have your DBContext class in a separate assembly from your ASP.NET Core web project? I can't seem to get this to work in this scenario without explicitly specifying a design time factory, and when I do, I'm having some differences in terms of config resolution. Specifically, I'm getting:

Your target project 'Project.Web' doesn't match your migrations assembly 'Project.Data.SQLEF'. Either change your target project or change your migrations assembly.
Change your migrations assembly by using DbContextOptionsBuilder. E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("Project.Web")). By default, the migrations assembly is the assembly containing the DbContext.
Change your target project to the migrations project by using the Package Manager Console's Default project drop-down list, or by executing "dotnet ef" from the directory containing the migrations project.

I've tried following both pieces of advice but would really like to get this without implementing the design time factory if possible. Thanks for any suggestions.

Update: to clarify a bit, it's my understanding that to follow the suggestion of " E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("Project.Web")" that I would need to implement the designtime factory. Not sure if I'm correct tho.

@benrhere Ideally, EF can find and use your DbContext as it is used in your application. For web apps, that usually means using the BuildWebHost pattern as described in the link above. In that situation, the same configuration will be used in both cases, and there should be no need to use the design-time factory. In other words, doing something like specifying the MigrationsAssembly does not mean that you need to use the design-time factory.

That being said, if tools are not picking up the correct configuration, or if the environment that the tool is running in does not have access to everything that the runtime has access to, then that's when the design-time factory is needed. It's not possible to know this without knowing the specifics of your environment. If you think you have hit a bug where the design-time factory is needed when it should not be, then please file a new issue with a runnable project/solution that demonstrates the issue so that we can investigate.

Hi @ajcvickers ,

For web apps, that usually means using the BuildWebHost pattern as described in the link above.

Can you confirm if you're referring to this link:

https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/

And if so, can you give me a bit more detail about how to implement the BuildWebHost pattern in a scenario where I'm in a class library without a main entry point? Where should that code live? Also, I presume this is similar to the code you're suggesting I implement a second time:

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup()
.Build();

@benhere That is the link that I meant. But if you are purely making a class library, then BuildWebHost is probably not the right choice. The docs for design-time DbContext may help: https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation

Note also that if your class library targets .NET Standard, then consider targeting .NETCoreApp instead. Othereise, you'll likely need to have a dummy .NET Core app to use for tooling--usually just a simple console app is fine. (@bricelam / @divega Is there a doc page for this?)

Hi @ajcvickers , I don't think this is a problem with the EF code. But per your suggestion, I think it will be simpler to have you take a look at a simple example I put together and attached. A few notes:

-I don't care what type of project houses my data types. I just care that there's a boundary of separation between them and my web project.
-For some reason this example is currently giving an exception about a db provider not being configured. I'm not sure why, but I'm hoping it doesn't prevent you from helping me find a solution.
-I left some relevant comments in the db context code.
-To reiterate my ultimate goal: I just want to be able to run the command "dotnet ef migrations add abc" and have it work, without having to hardcode the path to my config so I will still respect the development json file if appropriate.
-Feel free to change anything in this solution to help me achieve my goal. I'll follow the pattern you lay out.

Thank you in advance for any help.

EF Migrations Help.zip

Update: I fixed the problem which was throwing an exception and replaced the zip file above.

Update 2: Updated zip again to ensure that dotnet ef command will run (was missing reference to appropriate design nugget package)

I've updated the project above, and can boil down the above to a much simpler question now:

What steps do I need to take in order to be able to run "dotnet ef migrations add xyz" and have it work successfully? I don't care where it places the migrations files.

I was finally able to figure this out. There were a few things that tripped me up:

To Start:

C:\Users\ben.rubinger\Desktop\EF Migrations Help\EF Migrations Help>dotnet ef migrations add new
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]

      User profile is available. Using 'C:\Users\ben.rubinger\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.0.1-rtm-125 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Your target project 'EF Migrations Help' doesn't match your migrations assembly 'Data'. Either change your target project or change your migrations assembly.
Change your migrations assembly by using DbContextOptionsBuilder. E.g. options.UseSqlServer(connection, b => b.MigrationsAssembly("EF Migrations Help")). By default, the migrations assembly is the assembly containing the DbContext.
Change your target project to the migrations project by using the Package Manager Console's Default project drop-down list, or by executing "dotnet ef" from the directory containing the migrations project.

C:\Users\ben.rubinger\Desktop\EF Migrations Help\EF Migrations Help>

From here, I moved into the data project directory:
C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new

No executable found matching command "dotnet-ef"

This was because I didn't have:

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
  </ItemGroup>

in the relevant CSProj.

After I did this:

C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new

Startup project 'Data.csproj' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the Entity Framework Core .NET Command Line Tools with this project, add an executable project targeting .NET Core or .NET Framework that references this project, and set it as the startup project using --startup-project; or, update this project to cross-target .NET Core or .NET Framework.

C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>

Note that my solution did not involve following this advice. Instead, I finally figured out that I had to set the startup project:
C:\Users\ben.rubinger\Desktop\EF Migrations Help\Data>dotnet ef migrations add new --startup-project "..\EF Migrations Help"

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\ben.rubinger\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 2.0.1-rtm-125 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
The name 'new' is used by an existing migration.

and it finally worked! It seemed counterintuitive that I need to run the command from the data directory, and point it to the original webapp directory, yet this gave me success. I'm posting this here in case others hit this. I struggled a bit with the bits and pieces of documentation.

Update: while the above is true for the sample project, for my actual project I had a last hurdle. My data project was targeting .netcoreapp2.0, and I got an error stating "The specified framework 'Microsoft.NETCore.App', version '2.0' was not found". I had to switch to .netstandard2.0 to finally get it working. Quite a few hurdles. I didn't think this was a very strange scenario I was trying to achieve.

@benrhere Thanks for the detailed info! The startup folder was what was tripping me up too.

Glad that this could help you, @gecko-8 . I was really surprised by how many levels I had to hop through to get this working.

Was this page helpful?
0 / 5 - 0 ratings