4

As mentioned in this post I am seeding database data on the Startup of an ASP.NET Core project as follows:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {

  using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) {  
    var context = serviceScope.ServiceProvider.GetService<MyContext>();       
    context.Database.Migrate();
    context.EnsureSeedData();

  }

}

But the same post also recommends to seed data manually,

Please note that, in general, it is recommended to apply these operations manually (rather than performing migrations and seeding automatically on startup), to avoid racing conditions when there are multiple servers, and unintentional changes.

which I would prefer and using a dotnet tool as follows:

dotnet seed -c <Context> -s <SeedData>

where:
- Context is the DbContext to be used. Default is used if none is specified.
- SeedData is a class containing the Data Insertion logic.

Is it possible to create such a custom dotnet tool?

Can the DbContext be used inside the tool?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • You can create a console app and add seed command to dotnet client, in that way you can seed your db from command line, that makes sense ? – H. Herzl Feb 06 '17 at 22:41
  • Not sure. You mean a dotnet tool is a console application? Can you give an example? How to you access the context in the dotnet tool? – Miguel Moura Feb 06 '17 at 22:44
  • I mean you can create your console application that extends dotnet client, so you will can invoke "dotnet seed", also I don't know about your design but you need to move your db context to class library in order to reuse the same db context in web api and console aplication – H. Herzl Feb 06 '17 at 22:50
  • I am not sure about it ... I think there might be a way to create a custom command for dotnet and register it in the application as it would be with razor tools and so on ... – Miguel Moura Feb 06 '17 at 22:51
  • I did exactly what you are suggesting with a few differences. I am using EF6 in a separate library(.csproj) as I have found EF7 not quite ready for prime time (my main app is .net core). I created a console application (.csproj) that runs the migrations and seeds my database. I use this console app as part of my build pipeline before deploying my app. Works perfectly and I don't have to mess w/ seeding data at startup. – trevorc Feb 06 '17 at 22:55
  • @trevorc I had something like that but on a test which I would run when I needed to seed data ... I was also considering having it win in Program.cs when I run dotnet run and pass arguments. But I think having a proper command for it would be the best option – Miguel Moura Feb 06 '17 at 22:57
  • Have you checked this? http://stackoverflow.com/questions/41352970/writing-custom-code-generator-for-dotnet-core/41404005?noredirect=1#comment70372179_41404005 I know this question is about code generation but is related about enable commands, maybe is useful – H. Herzl Feb 06 '17 at 22:59
  • I use the command line tool for my integration testing using a test db and then after my tests pass I run the tool on my dev/staging/prod server. Shameless plug, I am in the middle of writing up a [wiki](https://github.com/trevorchunestudy/net-core-e2e/wiki/LMS.Setup.Tests) that documents this part of a sample application I have on github. It might help you. – trevorc Feb 06 '17 at 23:02
  • @H.Herzl how do you resolve the DbContext inside the dotnet tool? I am going to take look at Migrations but I think it would be something simpler – Miguel Moura Feb 06 '17 at 23:07
  • @trevorc I checked your code but when you seed the database you define that specific Context ... I was trying to crate something more generic that would be used as: dotnet seed -s MyDataSeedClass ... MyDataSeedClass could implement an interface with a method Run to execute the seed code. Just not sure how to create the Context in DataSeed class now that it is decoupled from Startup. I believe I would ONU need the connection string ... But would I pass it in the tool as argument or somehow get it inside MyDataSeedClass? What do you think about this approach? – Miguel Moura Feb 06 '17 at 23:16
  • @Miguel Moura. I think that is a reasonable approach. I only have one context so I don't need to worry about that. However, I use a different connection string and pass that in as an argument and store that value as an argument in VSO. Seems like passing in a argument for the context would work as well. If I am understanding your requirements correctly. – trevorc Feb 06 '17 at 23:18
  • @trevorc Sure I also have only one Context but the idea is to reuse it across projects and also included in the publishing pipeline the same way as dotnet build our a gulp task – Miguel Moura Feb 06 '17 at 23:20
  • @MiguelMoura. One other note. I keep my context, migrations, and seed data in a separate project. My console app takes a dependency on that project so I have access too any context I define. – trevorc Feb 06 '17 at 23:22
  • You can get arguments from command line and check the name against all dbcontexts defined in your project (dll or api) – H. Herzl Feb 06 '17 at 23:31

1 Answers1

4

It's not trivial to create a general-purpose dotnet CLI tool that loads a project's assembly.

You can start digging around in the aspnet/EntityFramework.Tools and aspnet/EntityFramework repos. The general process flow is:

  1. dotnet calls dotnet-ef.dll
  2. dotnet-ef collects information about the project (e.g. target framework and runtime architecture) via MSBuild and calls ef.exe (or dotnet ef.dll)
  3. ef.exe loads Microsoft.EntityFrameworkCore.Design.dll and the project assembly (either in an AppDomain or via Reflection) and calls OperationExecutor
  4. Microsoft.EntityFrameworkCore.Design instantiates the DbContext and performs operations on it.

You can simplify this dramatically by making your tool less general-purpose and more specific to your project. And even further by just making a console app instead of a dotnet tool.

bricelam
  • 28,825
  • 9
  • 92
  • 117