dotnet ef
Add, script, bundle and apply EF Core migrations from the command line, with the right project, startup project and design-time setup.
dotnet ef migrations add|remove|list|script|bundle|has-pending-model-changes, dotnet ef database update|drop, dotnet ef dbcontext scaffold|optimize [--project <p>] [--startup-project <p>] [--context <ctx>] [-- <app args>]- Added in .NET EF Core 1.0 (installed as the dotnet-ef tool since EF Core 3.0)
Parameters
| Parameter | Type | Description |
|---|---|---|
$--project, -poptional | path= the project in the current folder | Project that contains the DbContext and receives the migration files. |
$--startup-project, -soptional | path= the project in the current folder | Project that is built and executed to create the DbContext (usually the API). Must reference Microsoft.EntityFrameworkCore.Design. |
$--context, -coptional | string= the only DbContext | DbContext type to use when the project has more than one. |
$--output-dir, -ooptional | path= Migrations | migrations add: folder for the generated files. |
$--connectionoptional | string | database update: connection string to use instead of the one the app configures. |
$--idempotent, -ioptional | flag= false | migrations script: wrap each migration in a check so the script runs safely on any database state. |
$--configurationoptional | string= Debug | Build configuration used to build the projects. |
$--optional | separator | Arguments after it are passed to the startup project's args at design time. |
Return value
process exit code0 on success. Non-zero when the build fails, the DbContext cannot be created at design time, the database command fails, or has-pending-model-changes finds model changes without a migration.
Errors
Fails with "Unable to create a 'DbContext' of type ..." when the design-time services cannot be built, and with a provider exception when the database is unreachable or a migration's SQL fails.
Changelog
- PHP EF Core 3.0dotnet ef is no longer part of the SDK; install the dotnet-ef tool.
- PHP EF Core 6.0migrations bundle creates a single-file executable (efbundle) that applies migrations; add --self-contained -r <rid> to include the runtime.
- PHP EF Core 8.0migrations has-pending-model-changes reports a model that has changed without a migration.
dotnet ef is a .NET tool (package dotnet-ef), not part of the SDK. Install it in the repository's tool manifest at the same major version as EF Core, 10.0.13 here, and add Microsoft.EntityFrameworkCore.Design to the startup project with PrivateAssets="all". A tool that is older than the runtime prints a version warning and can generate migrations that do not match the model.
How the tool finds your DbContext
Every command builds the startup project, loads its assembly and asks for a DbContext in this order: an IDesignTimeDbContextFactory<T> in the project, then the application's service provider (it runs Program.cs up to builder.Build() and stops before the app starts listening), then a parameterless constructor. Running your Program.cs means every side effect before Build() runs too: a configuration provider such as Key Vault, a required environment variable, an eager connection opened while registering services. Code after Build(), including a Migrate() call and hosted services, never runs under dotnet ef. When that fails you see "Unable to create a 'DbContext' of type ...". Add a design-time factory or make start-up tolerate the tool.
Project and startup project
With the DbContext in Orders.Data and configuration in Orders.Api, pass --project src/Orders.Data --startup-project src/Orders.Api. The migration files go to the first, the configuration and provider come from the second. Arguments after -- reach the startup project's args, for example -- --environment Staging.
Commands
| Command | Use |
|---|---|
migrations add AddOrderShippedAt | Generate a migration and update the model snapshot. Review the generated code: renames come out as drop plus add. |
migrations remove | Delete the last migration if it has not been applied anywhere shared. |
migrations list | Show migrations and, with a connection, which are applied. |
migrations script --idempotent | SQL for review or for a DBA; safe on any database state. |
migrations bundle | An executable (efbundle) that applies migrations, for a deploy job. |
migrations has-pending-model-changes | CI gate: fails when someone changed the model and forgot migrations add. |
database update [Migration] | Apply up to the latest, or migrate down to a named migration. |
dbcontext scaffold | Reverse-engineer entities from an existing database. |
dbcontext optimize | Generate a compiled model for faster start-up of very large models. |
Production
database update from a developer laptop against production is how schemas drift from source control. In a pipeline, generate an idempotent script or a bundle once per build, review it, and run it in a single deploy step (a Kubernetes Job or pipeline stage) before the new pods start. Since EF Core 9, Migrate() takes a database-wide migration lock, so replicas that migrate at start-up wait for each other instead of applying the same change twice. It is still the wrong place: every replica's start-up blocks on the schema change, a failed migration crash-loops every pod, and the app's connection string needs DDL permissions. Migrations that run before rollout must stay compatible with the version still serving traffic (expand, then contract).
Example
# One-time setup: pin the tool in the repo and add the design package.
dotnet new tool-manifest
dotnet tool install dotnet-ef --version 10.0.13
dotnet package add Microsoft.EntityFrameworkCore.Design --version 10.0.13 --project src/Orders.Api
# Day to day: DbContext lives in Orders.Data, configuration in Orders.Api.
dotnet ef migrations add AddOrderShippedAt \
--project src/Orders.Data --startup-project src/Orders.Api --output-dir Migrations
dotnet ef migrations list --project src/Orders.Data --startup-project src/Orders.Api
dotnet ef database update --project src/Orders.Data --startup-project src/Orders.Api
# Undo the last, unshared migration: roll the local database back first, then remove.
dotnet ef database update AddCustomerEmailIndex --project src/Orders.Data --startup-project src/Orders.Api
dotnet ef migrations remove --project src/Orders.Data --startup-project src/Orders.Api
# CI: fail if the model changed without a migration, then build deploy artefacts.
dotnet ef migrations has-pending-model-changes --project src/Orders.Data --startup-project src/Orders.Api
dotnet ef migrations script --idempotent -o artifacts/migrate.sql \
--project src/Orders.Data --startup-project src/Orders.Api
dotnet ef migrations bundle --self-contained -r linux-x64 -o artifacts/efbundle --force \
--project src/Orders.Data --startup-project src/Orders.ApiWhen to use it
- Fail a pull request with migrations has-pending-model-changes when an Order property was added without a migration
- Build an efbundle per release and run it as a Kubernetes Job before rolling out the new orders API pods
- Hand a DBA an idempotent SQL script for review before a schema change to the invoices table
Frequently asked questions
What does "Unable to create a 'DbContext' of type" mean?
The tool could not build your DbContext at design time: Program.cs threw, needed configuration it could not get, or the startup project is wrong. Check --startup-project, run with --verbose, or add an IDesignTimeDbContextFactory.
Should the API apply migrations at start-up?
Not in production. EF Core 9 and later serialise concurrent Migrate() calls with a lock, but every replica's start-up still blocks on the schema change, a failed migration crash-loops every pod, and the app needs a DDL-privileged connection string. Run a bundle or idempotent script once per deployment instead.
Why does dotnet ef say the tools version is older than the runtime?
The installed dotnet-ef is a different version from the EF Core packages. Update the tool in .config/dotnet-tools.json to the same version and run dotnet tool restore.
More examples
A design-time factory that never touches production
<code>migrations add</code> and <code>script</code> never open a connection, so the fallback string only matters for <code>database update</code>. The factory receives the arguments passed after <code>--</code> as <code>args</code>, which allows <code>-- --tenant eu</code> style switches.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
public sealed class Order
{
public int Id { get; set; }
public required string Number { get; set; }
public decimal Total { get; set; }
public DateTimeOffset? ShippedAt { get; set; }
}
public sealed class OrdersDbContext(DbContextOptions<OrdersDbContext> options) : DbContext(options)
{
public DbSet<Order> Orders => Set<Order>();
}
// Found by dotnet ef before it tries to run Program.cs, so design-time commands
// work without Key Vault, environment variables or a reachable database.
public sealed class OrdersDbContextFactory : IDesignTimeDbContextFactory<OrdersDbContext>
{
public OrdersDbContext CreateDbContext(string[] args)
{
var connectionString = Environment.GetEnvironmentVariable("ORDERS_DESIGN_DB")
?? "Host=localhost;Port=5432;Database=orders_dev;Username=orders_dev";
var options = new DbContextOptionsBuilder<OrdersDbContext>()
.UseNpgsql(connectionString, npgsql => npgsql.MigrationsAssembly("Orders.Data"))
.Options;
return new OrdersDbContext(options);
}
}Applying a bundle in the deploy pipeline
The bundle built by <code>dotnet ef migrations bundle</code> runs once, before the new pods roll out. It reads <code>ConnectionStrings__Orders</code> from the environment through the startup project's configuration, so the password never appears in the process argument list as <code>--connection</code> would. The old pods keep serving against the new schema while it runs, so the migration must be backward compatible (expand, then contract). <code>backoffLimit: 0</code> stops a broken migration from being retried halfway through; the pipeline fails and a person looks.
apiVersion: batch/v1
kind: Job
metadata:
name: orders-migrate-1-4-2
namespace: shop
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: registry.example.com/shop/orders-migrations:1.4.2
command: ["./efbundle"]
env:
- name: ConnectionStrings__Orders
valueFrom:
secretKeyRef:
name: orders-db
key: connection-stringTool and design package in the projects
<code>PrivateAssets=all</code> keeps the design-time package out of anything that references the API, and it is not needed at run time. Keep it, the <code>dotnet-ef</code> tool and the EF Core runtime packages on the same version, 10.0.13.
<!-- src/Orders.Api/Orders.Api.csproj (startup project) -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Orders.Data/Orders.Data.csproj" />
</ItemGroup>
</Project>
Discussion