After publishing a .Net Core RC1 application, commands specified in the project.json had corresponding .cmd files created for them which could be executed after deployment (e.g.
Within my context I have this hack from here
// Hack added so EntityFrameworkCore\Add-Migration initial works
public class ApplicationContextDbFactory : IDesignTimeDbContextFactory<MyContext>
{
MyContext IDesignTimeDbContextFactory<MyContext>.CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var optionsBuilder = new DbContextOptionsBuilder<MyContext>();
optionsBuilder.UseSqlServer(configuration.GetConnectionString("StreamCheckpointContextDB"));
return new MyContext(optionsBuilder.Options);
}
}
I made my Program.Main read like this...
public static void Main(string[] args)
{
if (args.Contains("JustMigrateMe"))
{
IDesignTimeDbContextFactory<MyContext> factory = new ApplicationContextDbFactory();
var ctx = factory.CreateDbContext(new string[0]);
ctx.Database.Migrate();
ctx.Dispose();
return;
}
// Other stuff
}
}
So to apply migrations I simply call the exe with the added argument.
Unfortunately EF Core migration s*cks, a lot... I have seen tons of solutions for this but lets do a list of them. So here is what you can do to run and deploy EF migrations without Visual Studio. None of the below is perfect solution all have some caveats:
IDesignTimeDbContextFactory<DbContext>
interface in order to make it work. Also make sure you have EF.dll and Microsoft.EntityFrameworkCore.Design.dll on your deploy server. The linked script is looking for those in numerous folders. Best is to copy it during build from your .nuget folders to your artifact. Sounds complicated, yes it is... But linked script helps a lot.dbContext.Database.Migrate();
Sorry the list got very long. But hope it helps.
I ended up in the same problem on a project but for several reasons I don't want migrations to run automatically on application boot.
To solve it I updated Program.cs
to take two arguments (full code is listed below)
--ef-migrate
, to apply all pending migrations, and--ef-migrate-check
, to validate if all migrations have been appliedIf arguments are present then the EF actions are applied and the program exits, otherwise the web application is launched.
Please note that it depends on the Microsoft.Extensions.CommandLineUtils
package to ease the command line parsing.
For octopus deploy one can then publish the package twice to seperate locations - one for running migrations and the other for webhosting. In our case, we added a "post deploy powershell script" with the content
$env:ASPNETCORE_ENVIRONMENT="#{Octopus.Environment.Name}"
dotnet example-app.dll --ef-migrate
In a docker context it would work perfectly too
docker run -it "example-app-container" dotnet example-app.dll --ef-migrate
Full Program.cs excluding namespace and usings:
//Remember to run: dotnet add package Microsoft.Extensions.CommandLineUtils
public class Program
{
public static void Main(string[] args)
{
var commandLineApplication = new CommandLineApplication(false);
var doMigrate = commandLineApplication.Option(
"--ef-migrate",
"Apply entity framework migrations and exit",
CommandOptionType.NoValue);
var verifyMigrate = commandLineApplication.Option(
"--ef-migrate-check",
"Check the status of entity framework migrations",
CommandOptionType.NoValue);
commandLineApplication.HelpOption("-? | -h | --help");
commandLineApplication.OnExecute(() =>
{
ExecuteApp(args, doMigrate, verifyMigrate);
return 0;
});
commandLineApplication.Execute(args);
}
private static void ExecuteApp(string[] args, CommandOption doMigrate, CommandOption verifyMigrate)
{
Console.WriteLine("Loading web host");
//
// Please note that this webHostBuilder below is from an older
// dotnet core version. Newer dotnet cores have a simplified version
// Use that instead and just take the command line parsing stuff with you
var webHost = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
if (verifyMigrate.HasValue() && doMigrate.HasValue())
{
Console.WriteLine("ef-migrate and ef-migrate-check are mutually exclusive, select one, and try again");
Environment.Exit(2);
}
if (verifyMigrate.HasValue())
{
Console.WriteLine("Validating status of Entity Framework migrations");
using (var serviceScope = webHost.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetService<DatabaseContext>())
{
var pendingMigrations = context.Database.GetPendingMigrations();
var migrations = pendingMigrations as IList<string> ?? pendingMigrations.ToList();
if (!migrations.Any())
{
Console.WriteLine("No pending migratons");
Environment.Exit(0);
}
Console.WriteLine("Pending migratons {0}", migrations.Count());
foreach (var migration in migrations)
{
Console.WriteLine($"\t{migration}");
}
Environment.Exit(3);
}
}
}
if (doMigrate.HasValue())
{
Console.WriteLine("Applyting Entity Framework migrations");
using (var serviceScope = webHost.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetService<DatabaseContext>())
{
context.Database.Migrate();
Console.WriteLine("All done, closing app");
Environment.Exit(0);
}
}
}
// no flags provided, so just run the webhost
webHost.Run();
}
}
For EF Core 3.1 I succeeded with the following line ran in the release files folder. Of course the path to MyUser can be tweaked with
dotnet exec --depsfile ProjectName.deps.json --runtimeconfig ProjectName.runtimeconfig.json C:\Users\MyUser.nuget\packages\dotnet-ef\3.1.9\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll database update --context MyContext --assembly Project.dll --verbose
There is an extremely useful post with a solution to this problem here.
It worked for me (I had to tweak the commands a little bit, but it gave me a good basis to start).
In sum: you can replicate the dotnet ef database update
command by passing the ef.dll
(e.g. directly from your nuget folder (or from somewhere else if you don’t have nuget, since you are on a prod machine..)) with your .dll containing the migrations with some additional parameters (see below) to dotnet.exe
(or the linux equivalent).
For completeness here is the .cmd (also from the blogpost!)
set EfMigrationsNamespace=%1
set EfMigrationsDllName=%1.dll
set EfMigrationsDllDepsJson=%1.deps.json
set DllDir=%cd%
set PathToNuGetPackages=%USERPROFILE%\.nuget\packages
set PathToEfDll=%PathToNuGetPackages%\microsoft.entityframeworkcore.tools.dotnet\1.0.0\tools\netcoreapp1.0\ef.dll
dotnet exec --depsfile .\%EfMigrationsDllDepsJson% --additionalprobingpath %PathToNuGetPackages% %PathToEfDll% database update --assembly .\%EfMigrationsDllName% --startup-assembly .\%EfMigrationsDllName% --project-dir . --content-root %DllDir% --data-dir %DllDir% --verbose --root-namespace %EfMigrationsNamespace%
(A bash version if this cmd is in the blogpost)
Btw. this approach was also mentioned in many github issues: https://github.com/aspnet/EntityFramework.Docs/issues/328 https://github.com/aspnet/EntityFramework.Docs/issues/180
ps: I found this in the blog of Ben Day, so all credit goes to Ben!