📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

How do you apply EF Core migrations safely in a CI/CD pipeline using dotnet ef, and why not just call Database.Migrate() at application startup?

Covers dotnet ef migrations in CI/CD: --project/--startup-project, design-time factories, idempotent SQL scripts, migration bundles, pending model change checks, and why startup migration is risky.

Senior RealWorldScenario Advanced ci-cd deployment dotnet-cli dotnet-ef ef-core migrations

🎯 Key points

  • dotnet-ef needs Microsoft.EntityFrameworkCore.Design and the right --project/--startup-project; IDesignTimeDbContextFactory builds the context at design time without booting the app
  • Review every generated migration and its SQL before production, like any other code change
  • migrations script --idempotent produces SQL safe to run against any earlier version
  • migrations bundle produces an executable that applies pending migrations with --connection
  • Migrating at startup races across instances, needs DDL rights, and crash-loops on failure
  • Run migrations as a separate pre-deploy step under a dedicated deployment identity
  • Destructive changes (drops, renames scaffolded as drop and add) need expand/contract, backups and a tested roll-forward plan
  • has-pending-model-changes in CI catches model changes without a migration

⏱ 30-second answer

I generate migrations locally with dotnet ef migrations add, then make deployment a separate, reviewed step. The pipeline produces an artefact: either an idempotent SQL script (dotnet ef migrations script --idempotent) for a DBA to review, or a migration bundle (dotnet ef migrations bundle), a self-contained executable that applies pending migrations. That artefact runs once, before the new app version starts, with a dedicated deployment identity. I avoid Database.Migrate() at startup because several instances can race, the app needs DDL permissions it shouldn't have, and a failed migration turns into a crash loop.

🗣 Full answer (senior)

How I would answer this in an interview

"I generate migrations locally with dotnet ef migrations add, using a pinned local dotnet-ef, the Design package on the startup project, explicit --project and --startup-project, and an IDesignTimeDbContextFactory so the tool doesn't boot the whole app. I review every migration and its SQL like code. Then the build job produces an artefact: either an idempotent script for a DBA to review, or a migration bundle built self-contained for the agent's platform, which runs once as a pre-deploy step under a dedicated deployment identity, with the connection string passed in from the secret store.

For example, our [payments API] ran Database.Migrate() on startup across [six] instances, and a release added an index to a [40-million-row] table during [business hours]. I needed to unblock the rollout and stop schema changes ever coupling to app start again. I traced the stall to CREATE INDEX locks, halted the deployment, then moved us to a bundle built in CI and run before the slot swap under its own identity. I cut the app's database rights to read and write only, and added a has-pending-model-changes gate to the build. Deployments became [predictable], with [no] further migration-related incidents in [the following months].

That's why I avoid migrating at startup: instances queue on the lock, the app needs DDL rights it shouldn't have, and a failed migration becomes a crash loop. The mistake I see is dropping a column in the same release that stops using it, so I use expand and contract, confirm a backup first, and roll forward rather than trusting Down(). Schema change and application start are separate concerns, each with its own identity and approval."


The tooling pieces

  • dotnet-ef is the CLI tool, best installed as a pinned local tool.
  • Microsoft.EntityFrameworkCore.Design must be referenced by the startup project, otherwise dotnet ef cannot run.
  • --project points at the project that contains the DbContext and migrations; --startup-project points at the project the tool builds and runs to discover configuration and services.
  • IDesignTimeDbContextFactory<T> lets the tool create the context without booting the whole app host, which is useful when the startup needs secrets or services that aren't available at build time.

Authoring

dotnet ef migrations add AddCustomerAbn \
  --project src/Billing.Data --startup-project src/Billing.Api

Review the generated migration like any other code. Watch for renames that EF scaffolded as drop-and-add (data loss), and for long-running operations on big tables.

Deployment options

Option Command Good for
Idempotent SQL script dotnet ef migrations script --idempotent -o migrate.sql DBA review, change-controlled environments
Migration bundle dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle Pipelines that should not need the SDK or source at deploy time
dotnet ef database update Directly against a database Local development only
Database.Migrate() at startup In app code Small single-instance apps, dev and tests

An idempotent script wraps every migration in a check against __EFMigrationsHistory, so it is safe to run against a database at any earlier version. A bundle does the same job as an executable, and takes --connection at run time so the connection string comes from the pipeline's secret store.

Why not migrate at startup

  1. Concurrency: when five pods start together, they all try to migrate. Newer EF versions take a lock during Migrate(), but you still pay the startup cost on every instance and must think about it.
  2. Least privilege: the running app then needs ALTER/CREATE rights. In production the app identity should usually be limited to data access, and only the deployment identity should run DDL.
  3. Failure mode: a failed migration makes every instance crash on boot, and health checks restart them in a loop.
  4. No review gate: nobody sees the SQL before it hits production.

Production example

A payments API in Azure Australia East ran Migrate() in Program.cs. A migration that added an index to a 40-million-row table locked writes for minutes while every new pod waited on it, and the rollout stalled. We moved to a migration bundle built in CI, run as a pre-deployment job under a separate managed identity, and split large index changes into online operations scheduled out of hours.

Best practices, performance and security

  • Run dotnet ef migrations has-pending-model-changes in CI so a model change without a migration fails the build.
  • Keep migrations backward compatible with the version currently running (expand, migrate, contract), so you can deploy without downtime and roll the app back.
  • Never hard-code connection strings in a design-time factory; read them from environment variables or user secrets.
  • Treat the bundle or script as a versioned artefact, built once and promoted through environments.

🔍 What the interviewer is checking

Probes production maturity with EF Core and pipelines.

Green flags: knows --idempotent and bundles; argues least privilege and concurrency against startup migration; mentions backward-compatible (expand/contract) changes and a pending-changes check.

Red flags: "we just run database update" against prod; migrations from developer machines; no idea what __EFMigrationsHistory is.

📚 Learn in depth

1. The pieces dotnet ef needs

Piece Where it lives Why it matters
dotnet-ef tool Local tool manifest (.config/dotnet-tools.json) The CLI front end. Pin it to the same major and minor version as your EF Core packages
Microsoft.EntityFrameworkCore.Design Referenced by the startup project (usually with PrivateAssets="all") Contains the design-time services the tool loads. Without it you get "Your startup project doesn't reference Microsoft.EntityFrameworkCore.Design"
Migrations project (--project) The class library that holds the DbContext and the Migrations/ folder Where new migration files are written and read from
Startup project (--startup-project) The executable project, such as the Web API The tool builds it and loads its configuration and services to create the context
Provider package For example Microsoft.EntityFrameworkCore.SqlServer or Npgsql.EntityFrameworkCore.PostgreSQL Generates provider-specific SQL

2. How dotnet ef creates your DbContext

 dotnet ef migrations add AddCustomerAbn --project Data --startup-project Api
        |
        v
  1. dotnet build Api (which builds Data)
        |
        v
  2. Load Api's assembly plus Microsoft.EntityFrameworkCore.Design
        |
        v
  3. Create the DbContext, trying in this order:
     a) An IDesignTimeDbContextFactory<T> in the startup or migrations
        project                                   --> use it
     b) The app's host (WebApplication.CreateBuilder / CreateHostBuilder)
        --> run Program up to building the host, resolve T from DI
     c) A parameterless constructor on T          --> new it up
        |
        v
  4. Compare the current model with the model snapshot
     (Migrations/<Context>ModelSnapshot.cs)
        |
        v
  5. Write <timestamp>_AddCustomerAbn.cs, its .Designer.cs,
     and an updated snapshot

Step 3b is why design-time commands can fail in CI: building the host runs your Program.cs, which may try to read Key Vault, connect to a message broker or validate options. A design-time factory (3a) avoids all of that and gives the tool only what it needs.

If you must keep 3b, you can detect design time in Program.cs with EF.IsDesignTime (EF Core 9 and later) and skip startup work such as seeding or calling external services.

3. What a migration is made of

File Contains Edit by hand?
20261022010203_AddCustomerAbn.cs Up() and Down() methods with operations such as AddColumn, CreateIndex, Sql(...) Yes, when needed, for example to add a data backfill with migrationBuilder.Sql(...)
20261022010203_AddCustomerAbn.Designer.cs The model as it was at that migration, plus the [Migration] id attribute No
BillingContextModelSnapshot.cs The current model, used to compute the next diff No. Use dotnet ef migrations remove to undo the last unapplied migration

The database records applied migrations in the __EFMigrationsHistory table (columns MigrationId and ProductVersion). That table, not the files, is what decides which migrations still need to run.

4. The four ways to apply migrations

Option Command Who runs it Needs SDK and source at deploy time? Good for
Direct update dotnet ef database update A developer Yes Local development only
Idempotent SQL script dotnet ef migrations script --idempotent -o migrate.sql DBA or pipeline via sqlcmd / Invoke-Sqlcmd No Change-controlled environments where people review SQL
Migration bundle dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle Pipeline deploy job No Automated pipelines; one executable per release
Startup migration context.Database.Migrate() in Program.cs Every app instance on boot No Small single-instance apps, local dev, integration tests

Idempotent script

Without --idempotent, the script assumes the database is at a specific starting migration (by default, empty). With it, every migration is wrapped in a check against the history table, for example on SQL Server:

IF NOT EXISTS (SELECT * FROM [__EFMigrationsHistory]
               WHERE [MigrationId] = N'20261022010203_AddCustomerAbn')
BEGIN
    ALTER TABLE [Customers] ADD [Abn] nvarchar(11) NULL;
END;

So the same script is safe against a database at any earlier version. You can also script a range: dotnet ef migrations script AddOrders AddCustomerAbn produces only the changes between those two migrations.

Migration bundle

A bundle is a small executable that contains your compiled migrations, EF Core and the provider. It applies pending migrations, the same way database update would, but without the SDK, the source code or dotnet-ef on the deploy agent.

./efbundle --connection "$BILLING_DEPLOY_DB"          # apply all pending
./efbundle 20261001000000_AddOrders --connection ...  # migrate to a specific one
./efbundle --verbose --connection ...                  # print the SQL it runs

Build it for the deploy agent's platform (-r linux-x64 or -r win-x64) and with --self-contained if the agent has no .NET runtime.

5. Why not call Database.Migrate() at startup

  6 pods start together
  pod1 --Migrate()--> takes migration lock, runs CREATE INDEX (8 min)
  pod2 --Migrate()--> waits for lock ...
  pod3 --Migrate()--> waits for lock ...
  ...
  readiness probes time out --> orchestrator restarts pods --> they wait again
Problem Detail
Concurrency EF Core 9 and later take a database-level lock during Migrate(), so instances no longer corrupt each other, but every instance still blocks until the slowest migration finishes
Least privilege The app identity needs ALTER and CREATE (often db_owner). If the app is compromised, so is the schema
Failure mode A failing migration makes every instance fail to start, so a schema problem becomes an outage
No review gate Nobody sees the SQL before it reaches production
Coupled rollback Rolling the app back doesn't roll the schema back, and the old version may not work with the new schema

Since EF Core 9, Migrate() also throws if the model has changes with no migration (the PendingModelChangesWarning is an error by default). That is helpful, but it means a forgotten migration is discovered at production startup instead of in CI.

6. Pipeline shape

  BUILD (once)                              DEPLOY (per environment)
  -------------------------------           ----------------------------------
  dotnet tool restore                       download artefacts
  dotnet build -c Release                   approval gate (prod)
  dotnet ef migrations                      ./efbundle --connection "$DB"
     has-pending-model-changes  <- gate        (runs as deploy identity)
  dotnet ef migrations bundle               failed? stop here, app unchanged
  dotnet ef migrations script               deploy new app version
     --idempotent  (for review)             smoke tests
  publish app + efbundle + migrate.sql

Build the bundle once and promote the same file through test, staging and production. The connection string comes from the pipeline's secret store (an Azure Key Vault-linked variable group, GitHub environment secrets) at deploy time, never from the repo.

7. Backward-compatible changes: expand and contract

During a rolling deploy, old and new app versions run against the same database. Every migration must therefore work with the version currently running.

To rename Phone to MobileNumber with no downtime:

Release Schema change App change
1. Expand Add MobileNumber (nullable). Backfill it in batches Write to both columns, read Phone
2. Switch None Read and write MobileNumber
3. Contract Drop Phone None

EF scaffolds a property rename as RenameColumn, and some renames (or type changes) as drop and add, which loses data. Read every generated migration before you commit it.

8. Risky operations to look for in review

Operation Risk Safer approach
CreateIndex on a large table Blocks writes while it builds On SQL Server Enterprise or Azure SQL, use migrationBuilder.Sql("CREATE INDEX ... WITH (ONLINE = ON)"); on PostgreSQL, CREATE INDEX CONCURRENTLY (outside a transaction)
AlterColumn to non-nullable Fails if nulls exist, or rewrites the table Backfill first, then alter in a later migration
DropColumn / DropTable Data loss, breaks the running old version Expand and contract; back up first
Big data updates in Sql(...) Long transaction, log growth, lock escalation Batch the update, or run it as a separate job
Changing a column type Possible table rebuild and truncation Add a new column, copy, switch, drop

9. Transactions, failure and rollback

  • EF Core runs each migration in a transaction where the provider supports transactional DDL. On SQL Server and PostgreSQL a failure usually rolls back that migration cleanly. MySQL does not support transactional DDL, so a failure can leave a half-applied migration.
  • Some statements can't run in a transaction (for example CREATE INDEX CONCURRENTLY on PostgreSQL). Use migrationBuilder.Sql("...", suppressTransaction: true) and plan for partial failure.
  • Prefer rolling forward: write a new migration that corrects the problem. Down() methods are rarely tested and often lose data.
  • If you do need a reverse script, generate it explicitly, for example dotnet ef migrations script AddCustomerAbn AddOrders (from a later migration to an earlier one), and review it.
  • Before a destructive release, confirm a backup or point-in-time restore window (Azure SQL keeps 7 to 35 days by default depending on tier).

10. Diagnosing common failures

Error Usual cause Fix
"Unable to create a 'DbContext' of type ..." The host failed to build at design time, or no constructor fits Add an IDesignTimeDbContextFactory<T>, or run with --verbose to see the inner exception
"Your startup project ... doesn't reference Microsoft.EntityFrameworkCore.Design" Missing package on the startup project Add it to the startup project, not only the data project
"The EF Core tools version ... is older than that of the runtime" dotnet-ef and EF packages differ Update the version in the tool manifest
"The model for context ... has pending changes" Someone changed the model without a migration Run dotnet ef migrations add, and add has-pending-model-changes to CI
Idempotent script fails to compile on SQL Server A batch references a column created or dropped in the same script Generate the script with a current EF version, which wraps such statements in EXEC, or split the migration

Add --verbose to any dotnet ef command to see the build output, which context it picked and the full stack trace.

11. Security

  • Use two identities: a deployment identity with DDL rights, used only by the migration step, and an app identity limited to data access (for example db_datareader and db_datawriter, or specific grants).
  • Prefer managed identities or workload identity federation over passwords in connection strings.
  • Never commit connection strings in a design-time factory. Read them from an environment variable or user secrets.
  • Keep the generated SQL script as a build artefact. It is your audit trail of what changed in production.

12. .NET 11 awareness

EF Core 11 is due with .NET 11 in November 2026, as a standard-term (STS) release, and is at the preview and release-candidate stage now. Expect the same dotnet ef commands and the same pipeline shape. Check the EF Core 11 "what's new" and breaking-changes pages before upgrading, and keep dotnet-ef in step with the packages. Production systems should stay on EF Core 10 (LTS) until you have tested the upgrade.

13. Try it yourself

  1. Create a Web API (Api) and a class library (Data) with a DbContext that uses SQLite. Reference Microsoft.EntityFrameworkCore.Design from Api.
  2. Run dotnet new tool-manifest and dotnet tool install dotnet-ef so the tool is pinned locally.
  3. Run dotnet ef migrations add Init --project Data --startup-project Api and open the three generated files.
  4. Run dotnet ef migrations script --idempotent -o migrate.sql and find the IF NOT EXISTS (or SQLite equivalent) checks.
  5. Build a bundle with dotnet ef migrations bundle --project Data --startup-project Api -o efbundle, then run ./efbundle --connection "Data Source=app.db" twice. The second run applies nothing.
  6. Add a property to an entity without adding a migration, and run dotnet ef migrations has-pending-model-changes. Note the non-zero exit code that would fail a CI step.
  7. Rename a property, add a migration and check whether EF generated RenameColumn or a drop and add.

💻 Code

namespace Billing.Data;

// Used only by dotnet ef at design time; never by the running app.
public sealed class BillingContextFactory : IDesignTimeDbContextFactory<BillingContext>
{
    public BillingContext CreateDbContext(string[] args)
    {
        var connection = Environment.GetEnvironmentVariable("BILLING_DESIGN_DB")
            ?? "Server=(localdb)\\mssqllocaldb;Database=BillingDesign;Trusted_Connection=True";

        var options = new DbContextOptionsBuilder<BillingContext>()
            .UseSqlServer(connection)
            .Options;

        return new BillingContext(options);
    }
}
# Azure Pipelines (excerpt)
- script: dotnet tool restore
- script: dotnet ef migrations has-pending-model-changes --project src/Billing.Data --startup-project src/Billing.Api
  displayName: Fail if the model has no matching migration
- script: >
    dotnet ef migrations bundle
    --project src/Billing.Data --startup-project src/Billing.Api
    --configuration Release --self-contained -r linux-x64
    -o $(Build.ArtifactStagingDirectory)/efbundle
  displayName: Build migration bundle

# Deploy stage, running under the deployment service connection
- script: ./efbundle --connection "$(BillingDbDeployConnection)"
  displayName: Apply migrations before swapping slots

⚠ Common mistakes

  • Mistake: calling Database.Migrate() at startup in a multi-instance service. Every instance blocks on the migration lock until the slowest migration finishes, readiness probes time out, and a failed migration stops every instance from starting. The app identity also needs DDL rights it shouldn't have. Instead: build a migration bundle or idempotent script in CI and run it once, as a separate pre-deployment step, under a dedicated deployment identity.

  • Mistake: running dotnet ef database update from a laptop against production. There is no review, no audit trail, and the result depends on whatever code and tool version happen to be on that laptop. Instead: apply migrations only from the pipeline, using the artefact built from the reviewed commit.

  • Mistake: applying a migration to production first. Run time, locking and data problems (nulls in a column you're making required, a huge table) only show up against realistic data. Instead: run the same bundle or script against a staging database restored from a recent, appropriately masked production copy, and time it.

  • Mistake: referencing Microsoft.EntityFrameworkCore.Design only from the data project, or mixing up --project and --startup-project. The tool loads design-time services from the startup project, and writes migrations to the migrations project. Getting these wrong produces confusing errors or migrations in the wrong folder. Instead: reference the Design package from the startup project, and always pass both options explicitly in scripts and pipelines.

  • Mistake: committing real connection strings in a design-time factory. The factory lives in source control, so the secret is shared with everyone who can read the repo, and stays in its history. Instead: read the design-time connection string from an environment variable or user secrets, and pass the deployment connection string to the bundle from the pipeline's secret store or use a managed identity.

  • Mistake: generating scripts without --idempotent. A plain script assumes the database is at a known starting migration. Run against a database at a different version, it fails or applies changes twice. Instead: use dotnet ef migrations script --idempotent for any environment whose version you don't control exactly.

  • Mistake: dropping a column in the same release that stops using it. During a rolling deploy the old version is still running and still queries that column, and an app rollback no longer works. Instead: use expand and contract: add the new shape, switch the app, then drop the old column in a later release.

  • Mistake: committing generated migrations without reading them. EF can scaffold a rename as a drop and add, which deletes data, or create an index that locks a large table for minutes. Instead: review every migration and its SQL (dotnet ef migrations script <previous> <new>) like any other code change, with a checklist for table size, locking and data loss.

  • Mistake: relying on Down() methods to undo a bad production migration. Down() is rarely tested, often loses data, and doesn't help if the app has already written data in the new shape. Instead: roll forward with a new corrective migration, and confirm a backup or point-in-time restore before destructive releases.

  • Mistake: letting the dotnet-ef tool version drift from the EF Core packages. A mismatched tool warns or fails, and can generate code for a different EF version. Instead: pin dotnet-ef in the local tool manifest and update it in the same pull request that updates the EF packages.

  • Mistake: no CI check for missing migrations. A model change without a migration is found at deploy time, or (since EF Core 9) as an exception when Migrate() runs. Instead: run dotnet ef migrations has-pending-model-changes in the build job so the pipeline fails early.

🏛 Senior insight: trade-offs

The senior move is to separate schema change from application start. Migrations become an artefact with its own identity, approval and failure handling, and the app only ever needs data permissions. Choose script versus bundle based on your organisation: a bank with a DBA change board wants reviewable SQL; a product team with automated environments prefers bundles.

Migration history and failure recovery are part of the design:

  • __EFMigrationsHistory is the source of truth for what has been applied. Never edit or delete a migration that has reached a shared environment; add a new one instead.
  • EF Core runs migrations inside transactions where the database provider supports it, so on SQL Server a failing migration usually rolls back cleanly. Some operations can't run in a transaction, so read the generated SQL for long or non-transactional steps.
  • Prefer roll forward (a new corrective migration) over running Down methods in production. If you need a reverse script, generate it explicitly with dotnet ef migrations script <From> <To> and review it.
  • For destructive changes, take a backup or confirm point-in-time restore (for example on Azure SQL) before the deploy, and keep the old app version compatible until the contract step.

Under the Australian Privacy Act, a destructive migration that silently drops personal data you were required to keep, or keeps data you were meant to delete, is a compliance problem as well as a technical one.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate mentions installing dotnet-ef. Why: Checks how the candidate keeps the dotnet-ef version aligned with the EF Core packages.
    How to answer

    A .NET tool is a console app shipped as a special NuGet package. Global tools (dotnet tool install -g) are installed per user and land on your PATH. Local tools are pinned per repository in a manifest, .config/dotnet-tools.json, created with dotnet new tool-manifest. Anyone who clones the repo runs dotnet tool restore and gets exactly the same versions. For team and CI work I prefer local tools, because the version lives in source control next to the code that depends on it.

  2. When: Candidate builds a migration bundle with --self-contained -r linux-x64. Why: Bundles take a RID and self-contained option; checks understanding of those publish concepts.
    How to answer

    By default dotnet publish gives a framework-dependent folder that needs the .NET runtime on the server. Self-contained (--self-contained -r linux-x64) bundles the runtime, so nothing needs installing, but the output is bigger and you patch the runtime by redeploying. On top of that: single-file packs everything into one executable, trimming removes unused code, ReadyToRun precompiles to speed up start-up, and Native AOT compiles to a native binary with very fast start-up and low memory but no dynamic code generation. The SDK can also build a container image directly. I choose based on where it runs, start-up and memory needs, and whether the libraries are trim- and AOT-safe, and I measure the result.

  3. When: Candidate describes a build job that produces the bundle. Why: Looks at the rest of the pipeline the migration step lives in.
    How to answer

    The pipeline restores three times, builds in Debug, runs dotnet format in a way that silently fixes files instead of failing, and publishes from a different build than the one it tested. I'd use global.json for the SDK, restore once with --locked-mode, build once in Release with --no-restore, then test and publish with --no-build. I'd run dotnet format --verify-no-changes so formatting drift fails the build, collect test results and coverage, and cache the NuGet folder.

🛠 Real-world problems: how you handle them

Startup migration stalls a rollout during peak trading
Situation
An ASP.NET Core payments API on .NET 10 ran six instances in Azure App Service (Australia East). Program.cs called Database.Migrate(). A release added an index on a 40-million-row transactions table.
Signals
Deployment stuck at 'starting'; health checks timing out; SQL wait stats showing LCK_M_* waits; p99 latency on the old instances jumped; App Service restarted instances repeatedly.
Investigation
Checked App Service logs and found every new instance blocked inside Migrate(). Queried sys.dm_exec_requests to see the CREATE INDEX holding locks. Confirmed the app's managed identity had db_owner, which is why it could run DDL at all.
Resolution
Immediate: stopped the rollout, let the index finish out of hours, then redeployed. Long term: removed Migrate() from startup, built a migration bundle in CI, ran it in a pre-deployment job under a separate deployment identity, reduced the app identity to db_datareader/db_datawriter, and used online index creation for large tables.
Prevention
Pipeline gate with has-pending-model-changes; migration review checklist (table size, locking, data loss); expand/contract policy; alert on deployment duration.

How to tell it (STAR)

**Situation:** Our payments API at [company] ran Database.Migrate() on startup across [six] instances, and a release added an index to a [40-million-row] table during [business hours]. **Task:** I needed to unblock the rollout and stop schema changes from ever coupling to app start again. **Action:** I traced the stall to CREATE INDEX locks via [sys.dm_exec_requests], halted the deployment, then moved us to an EF migration bundle built in CI and run as a pre-deploy step under a dedicated identity. I also cut the app's database rights to read/write only and added a has-pending-model-changes gate. **Result:** Deployments went from [unpredictable] to [a steady N minutes], we had no further migration-related incidents in [period], and the security review signed off on least-privilege database access.

↩ Usually asked after

More in .NET CLI

Last updated 1 Nov 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me