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. web.cmd and ef.cmd). In my case, I want to run the following Entity Framework command on my deployment target:
dotnet ef database update -c MyContext
This works fine when I run this from the folder containing the source code, however after publishing, it doesn't appear to find the command within the compiled DLLs. My understanding of the change in commands with RC2 is that 'tools' can be compiled as standalone applications named dotnet-*.dll and can be executed via the CLI. How can the Entity Framework Core tools be exposed as executable DLLs in the published output?
FYI, my build/deployment workflow is as follows:
TeamCity
dotnet restore => dotnet build => dotnet test => dotnet publish
Octopus Deploy
Upload Package => EF Update Database => etc
The command-line interface (CLI) tools for Entity Framework Core perform design-time development tasks. For example, they create migrations, apply migrations, and generate code for a model based on an existing database. The commands are an extension to the cross-platform dotnet command, which is part of the .
You can use EF Core in APIs and applications that require the full . NET Framework, as well as those that target only the cross-platform .
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();
dotnet ef migrations script --output <pathAndFile>.sql --context <DbContextName> --idempotent
. The output is an SQL file which can be executed manually or by a script in CI/CD pipeline..csproj
file is. So it requires source code! Also you have to change your code a bit. Need to implement IDesignTimeDbContextFactory<DbContext>
.UPDATE: In .NET 5 there is some improvements. It is now easier to implement and make use of IDesignTimeDbContextFactory but most importantly Microsoft fixed this bug. Now it is possible to pass an SQL connection string as args
. So if you implemented IDesignTimeDbContextFactory<T>
it is simple to use it with .NET CLI and EF tool:
dotnet ef database update --context <DbContextName> --project "**/<ProjectName>.csproj" -- "<SQL connection will be passed into args[0]>"
Also important to emphasize this works only with .NET 5 and requires source code as well! You can also use it with Option 6 (generate SQL script).
Second annoying issue once implemented IDesignTimeDbContextFactory<T>
this will be discovered by ALL ef
commands (even commands run from Visual Studio during development). If you require SQL connection string from args[0] you have to pass it in during development migrations add
or for any other ef
command!
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(); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With