<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Ntirintis</title>
    <description>The latest articles on DEV Community by John Ntirintis (@johnntirintis).</description>
    <link>https://dev.to/johnntirintis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1161300%2F7230ad2f-d03f-44e1-9d76-06e3be4f2c95.jpg</url>
      <title>DEV Community: John Ntirintis</title>
      <link>https://dev.to/johnntirintis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnntirintis"/>
    <language>en</language>
    <item>
      <title>My first post on DEV! A guide to EF Core migration bundles: the concurrency problem they solve, .bat scripts to create and run them, CI/CD stuff &amp; more!</title>
      <dc:creator>John Ntirintis</dc:creator>
      <pubDate>Fri, 17 Jul 2026 18:06:25 +0000</pubDate>
      <link>https://dev.to/johnntirintis/my-first-post-on-dev-a-guide-to-ef-core-migration-bundles-the-concurrency-problem-they-solve-396a</link>
      <guid>https://dev.to/johnntirintis/my-first-post-on-dev-a-guide-to-ef-core-migration-bundles-the-concurrency-problem-they-solve-396a</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896" class="crayons-story__hidden-navigation-link"&gt;EF Core Migration Bundles: What They Are &amp;amp; Why They Matter&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/johnntirintis" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1161300%2F7230ad2f-d03f-44e1-9d76-06e3be4f2c95.jpg" alt="johnntirintis profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/johnntirintis" class="crayons-story__secondary fw-medium m:hidden"&gt;
              John Ntirintis
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                John Ntirintis
                
              
              &lt;div id="story-author-preview-content-4168611" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/johnntirintis" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1161300%2F7230ad2f-d03f-44e1-9d76-06e3be4f2c95.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;John Ntirintis&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 17&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896" id="article-link-4168611"&gt;
          EF Core Migration Bundles: What They Are &amp;amp; Why They Matter
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dotnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dotnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/csharp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;csharp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/efcore"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;efcore&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cicd"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cicd&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            13 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>cicd</category>
      <category>database</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>EF Core Migration Bundles: What They Are &amp; Why They Matter</title>
      <dc:creator>John Ntirintis</dc:creator>
      <pubDate>Fri, 17 Jul 2026 18:04:10 +0000</pubDate>
      <link>https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896</link>
      <guid>https://dev.to/johnntirintis/ef-core-migration-bundles-what-they-are-why-they-matter-3896</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb7ymvampgyt1afnpkoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frb7ymvampgyt1afnpkoz.png" alt="EF Migrations Bundle Hero Image" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A migration bundle packages every pending migration for &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;DbContext&lt;/code&gt; into a single, runtime-targeted executable. No source code or &lt;code&gt;dotnet-ef&lt;/code&gt; tool needed on the target machine.&lt;/li&gt;
&lt;li&gt;They exist to get schema changes out of your running application and into a single, deliberate, auditable step, which is what makes them safe for production, CI/CD, and multi-service/shared-database setups.&lt;/li&gt;
&lt;li&gt;They're idempotent, one-per-context, and need an &lt;code&gt;appsettings.json&lt;/code&gt; sitting next to them with the right &lt;em&gt;keys&lt;/em&gt; present (not necessarily real values).&lt;/li&gt;
&lt;li&gt;The tool sometimes logs in red when there's nothing to do. That's not a failure, check the last line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This assumes you're already using EF Core Migrations (&lt;code&gt;dotnet ef migrations add&lt;/code&gt;, &lt;code&gt;dotnet ef database update&lt;/code&gt;) or at least have some familiarity with it and just haven't used bundles yet. If you're new to EF Core Migrations entirely, get comfortable with the basic workflow first, bundles are a deployment mechanism on top of migrations you've already created, not a replacement for them!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Exactly Is a Migration Bundle
&lt;/h2&gt;

&lt;p&gt;A bundle is a self-contained executable that packages every pending EF Core migration for a single &lt;code&gt;DbContext&lt;/code&gt;, targetable at a specific runtime (&lt;code&gt;win-x64&lt;/code&gt;, &lt;code&gt;linux-x64&lt;/code&gt;, &lt;code&gt;osx-x64&lt;/code&gt;, etc.). Build it once, and run it anywh-... oh wait thats Java, whoops! What i meant to say is, you only have to bundle it once, and you can run it against a database and you are done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet ef migrations bundle &lt;span class="nt"&gt;--self-contained&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; linux-x64 &lt;span class="nt"&gt;--context&lt;/span&gt; OrdersDbContext &lt;span class="nt"&gt;-o&lt;/span&gt; ./bundles/orders/efbundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR, for windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet ef migrations bundle &lt;span class="nt"&gt;--self-contained&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; win-x64 &lt;span class="nt"&gt;--context&lt;/span&gt; OrdersDbContext &lt;span class="nt"&gt;-o&lt;/span&gt; ./bundles/orders/efbundle.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! &lt;code&gt;--context&lt;/code&gt; picks the &lt;code&gt;DbContext&lt;/code&gt;, &lt;code&gt;-r&lt;/code&gt; picks the target platform, &lt;code&gt;--self-contained&lt;/code&gt; decides whether the .NET runtime ships inside the executable. There are a few more flags worth knowing once you're building these for real projects and CI/CD; full breakdown further down, in Anatomy of the Build Command.&lt;/p&gt;

&lt;p&gt;Running it (in a windows system) is just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./efbundle.exe &lt;span class="nt"&gt;--connection&lt;/span&gt; &lt;span class="s2"&gt;"Server=prod-db;Database=Orders;User Id=deploy_user;Password=***"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem Bundles Actually Solve
&lt;/h2&gt;

&lt;p&gt;EF Core lets you apply migrations straight from your application at startup, via &lt;code&gt;context.Database.MigrateAsync()&lt;/code&gt;. It's the path of least resistance, and it's genuinely fine for local development. In production, it falls apart for a few concrete reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run more than one instance of the same app, and more than one of them can try to apply the same migration at the same time. Prior to EF Core 9 there was no built-in protection against this, two instances racing to alter the schema simultaneously is how you get deadlocks or, worse, a half-migrated table.&lt;/li&gt;
&lt;li&gt;The app process needs elevated permissions to alter schema, which cuts against the normal practice of running production workloads with the least privilege they need.&lt;/li&gt;
&lt;li&gt;You lose the ability to inspect or roll back the exact SQL before it hits production, the migration just runs, whatever it does, whenever the app happens to boot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EF Core 9 introduced a database-wide lock that &lt;code&gt;MigrateAsync&lt;/code&gt;, &lt;code&gt;Migrate&lt;/code&gt;, &lt;code&gt;dotnet ef database update&lt;/code&gt;, and bundles all now respect, so two instances can no longer stomp on each other mid-migration. That closes the worst failure mode, but it doesn't fix the underlying architecture: with runtime migrations, your application process &lt;em&gt;is&lt;/em&gt; the thing that owns and executes schema changes, on every boot, on every instance. A bundle is a separate, deliberate, one-shot execution instead, it runs once, sequentially, outside your app's lifecycle, and it's done.&lt;/p&gt;

&lt;p&gt;This matters even more once a database is shared across services! A modular monolith with several bounded contexts against one schema, or a set of microservices where no single service should &lt;strong&gt;unilaterally&lt;/strong&gt; own schema changes. In that setup you don't want N running instances each independently deciding to call &lt;code&gt;MigrateAsync&lt;/code&gt; against a schema they don't exclusively own. A bundle is a single, auditable, out-of-band operation you run against that shared environment. Deploy it, run it once and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Bundle, One Context, No Exceptions
&lt;/h2&gt;

&lt;p&gt;A bundle maps to exactly one &lt;code&gt;DbContext&lt;/code&gt;. There's no such thing as a single bundle that rolls up migrations for multiple contexts. If you have several (common in modular applications), you build one bundle per context, using &lt;code&gt;--context&lt;/code&gt; to specify it at build time. Design your build/deploy scripts around that from day one; it doesn't collapse down later no matter how many contexts you accumulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Misleading "Error" That Isn't One
&lt;/h2&gt;

&lt;p&gt;Run a bundle against a database that's already fully migrated, and it logs its findings in red text that reads like a failure before finishing with &lt;code&gt;Done.&lt;/code&gt; Here's an actual run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An error occurred using the connection to database 'Platform' on server 'tcp://localhost:1234'.
No migrations were found in assembly 'Contoso.Platform.Modules'. A migration needs to be added before the database can be updated.
Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
No migrations were applied. The database is already up to date.
Done.
Press any key to continue . . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth clocking here. First, that opening line (&lt;em&gt;"An error occurred using the connection to database..."&lt;/em&gt;) reads like a connection failure. It isn't one, it's just the tool's log-level framing for "here's what I found when I connected." Second, this particular run also shows the EF Core 9 locking behavior mentioned earlier in practice: the &lt;em&gt;"Acquiring an exclusive lock for migration application"&lt;/em&gt; line is the bundle taking that database-wide lock before it does anything, exactly as documented.&lt;/p&gt;

&lt;p&gt;The tell is always the final line. If it says &lt;code&gt;Done.&lt;/code&gt;, the bundle succeeded regardless of what the red text above it says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The appsettings.json confusion (at least for me)
&lt;/h2&gt;

&lt;p&gt;The bundle needs an &lt;code&gt;appsettings.json&lt;/code&gt; sitting next to it in the execution directory. But! Not because it actually uses the values inside it. What matters is that the &lt;strong&gt;keys&lt;/strong&gt; your &lt;code&gt;DbContext&lt;/code&gt; expects to find are present. If your context is wired up with a named connection string (e.g. &lt;code&gt;UseSqlServer("name=ConnectionStrings:Default")&lt;/code&gt;), the bundle throws if that key is missing from the config file entirely even though you're overriding the actual value with &lt;code&gt;--connection&lt;/code&gt; at runtime. The value itself can be complete garbage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ConnectionStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"this-value-is-never-actually-used"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in practice: ship a near-empty &lt;code&gt;appsettings.json&lt;/code&gt; with the &lt;em&gt;structure&lt;/em&gt; your &lt;code&gt;DbContext&lt;/code&gt;'s configuration expects, fake values and all, and always pass the real connection string via &lt;code&gt;--connection&lt;/code&gt; when you execute the bundle.&lt;/p&gt;

&lt;p&gt;There's a second, blunter failure mode worth knowing: if &lt;code&gt;appsettings.json&lt;/code&gt; isn't there &lt;strong&gt;at all&lt;/strong&gt;, the bundle doesn't get anywhere near "missing key" territory! It fails outright while trying to construct the &lt;code&gt;DbContext&lt;/code&gt;, and says so plainly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unable to create a 'DbContext' of type 'Contoso.Modules.Billing.Infrastructure.BillingDbContext'. The
exception 'The configuration file 'appsettings.json' was not found and is not optional. The expected
physical path was 'C:\Work\Contoso\artifacts\migration-bundles\appsettings.json'.' was thrown while
attempting to create an instance. For the different patterns supported at design time, see
https://go.microsoft.com/fwlink/?linkid=851728
Press any key to continue . . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the red-herring "no migrations applied" output above, this one is a genuine, unambiguous failure. It's the clearest confirmation that the file's &lt;em&gt;presence&lt;/em&gt; is a hard requirement, independent of whether anything inside it is actually used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency
&lt;/h2&gt;

&lt;p&gt;Running a bundle against an already-current database is completely safe! It applies nothing and exits cleanly. Running it twice, ten times, or as part of every single deployment doesn't break anything; it only ever applies migrations that haven't been applied yet. That's what makes it safe to wire into a deploy step that runs unconditionally on every release, rather than something you have to remember to skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Bundle Creation and Execution
&lt;/h2&gt;

&lt;p&gt;Once you have more than one or two contexts, typing out the full &lt;code&gt;dotnet ef migrations bundle&lt;/code&gt; command with the right &lt;code&gt;--context&lt;/code&gt;, &lt;code&gt;-r&lt;/code&gt;, and output path every time gets old fast, even copy-pasting it gets tiring after a while. Thats why I suggest using two &lt;code&gt;.bat&lt;/code&gt; files, and they pay for themselves almost immediately. These are lightly genericized versions of what I'm actually running against a multi-context modular setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;build-migration-bundles.bat&lt;/code&gt;&lt;/strong&gt;: one build per context, self-contained, targeting &lt;code&gt;win-x64&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;setlocal&lt;/span&gt;
&lt;span class="c"&gt;REM ============================================================================&lt;/span&gt;
&lt;span class="c"&gt;REM build-migration-bundles.bat&lt;/span&gt;
&lt;span class="c"&gt;REM&lt;/span&gt;
&lt;span class="c"&gt;REM Builds all four EF Core migration bundles (win-x64, self-contained) into&lt;/span&gt;
&lt;span class="c"&gt;REM artifacts\migration-bundles\. Run from anywhere - it resolves paths&lt;/span&gt;
&lt;span class="c"&gt;REM relative to the repo root automatically.&lt;/span&gt;
&lt;span class="c"&gt;REM ============================================================================&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="na"&gt;/d &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="vm"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;REM adjust the line below if this .bat lives somewhere other than the repo root&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"REPO_ROOT=&lt;/span&gt;&lt;span class="nv"&gt;%cd%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"OUT=&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\artifacts\migration-bundles"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;exist&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="kd"&gt;Building&lt;/span&gt; &lt;span class="kd"&gt;migration&lt;/span&gt; &lt;span class="kd"&gt;bundles&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;Release&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;/4&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;ExampleServiceDbContext&lt;/span&gt;...
&lt;span class="kd"&gt;dotnet&lt;/span&gt; &lt;span class="kd"&gt;ef&lt;/span&gt; &lt;span class="kd"&gt;migrations&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\Contoso.ExampleService.Api\Contoso.ExampleService.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--context &lt;/span&gt;&lt;span class="kd"&gt;ExampleServiceDbContext&lt;/span&gt; &lt;span class="na"&gt;--self-contained -r &lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="na"&gt;--configuration &lt;/span&gt;&lt;span class="kd"&gt;Release&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;\migrate-auth.exe"&lt;/span&gt; &lt;span class="na"&gt;--force
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="nl"&gt;:error&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;/4&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;OrdersDbContext&lt;/span&gt;...
&lt;span class="kd"&gt;dotnet&lt;/span&gt; &lt;span class="kd"&gt;ef&lt;/span&gt; &lt;span class="kd"&gt;migrations&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\BuildingBlocks\Platform.Modules\Platform.Modules.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--startup-project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\Contoso.Platform.Api\Contoso.Platform.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--context &lt;/span&gt;&lt;span class="kd"&gt;OrdersDbContext&lt;/span&gt; &lt;span class="na"&gt;--self-contained -r &lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="na"&gt;--configuration &lt;/span&gt;&lt;span class="kd"&gt;Release&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;\migrate-orders.exe"&lt;/span&gt; &lt;span class="na"&gt;--force
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="nl"&gt;:error&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;/4&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;BillingDbContext&lt;/span&gt;...
&lt;span class="kd"&gt;dotnet&lt;/span&gt; &lt;span class="kd"&gt;ef&lt;/span&gt; &lt;span class="kd"&gt;migrations&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\BuildingBlocks\Platform.Modules\Platform.Modules.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--startup-project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\Contoso.Platform.Api\Contoso.Platform.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--context &lt;/span&gt;&lt;span class="kd"&gt;BillingDbContext&lt;/span&gt; &lt;span class="na"&gt;--self-contained -r &lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="na"&gt;--configuration &lt;/span&gt;&lt;span class="kd"&gt;Release&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;\migrate-billing.exe"&lt;/span&gt; &lt;span class="na"&gt;--force
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="nl"&gt;:error&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;/4&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;CommonDataDbContext&lt;/span&gt;...
&lt;span class="kd"&gt;dotnet&lt;/span&gt; &lt;span class="kd"&gt;ef&lt;/span&gt; &lt;span class="kd"&gt;migrations&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\BuildingBlocks\Platform.Modules\Platform.Modules.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--startup-project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\Contoso.Platform.Api\Contoso.Platform.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--context &lt;/span&gt;&lt;span class="kd"&gt;CommonDataDbContext&lt;/span&gt; &lt;span class="na"&gt;--self-contained -r &lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="na"&gt;--configuration &lt;/span&gt;&lt;span class="kd"&gt;Release&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;\migrate-common.exe"&lt;/span&gt; &lt;span class="na"&gt;--force
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="nl"&gt;:error&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="kd"&gt;All&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="kd"&gt;bundles&lt;/span&gt; &lt;span class="kd"&gt;built&lt;/span&gt; &lt;span class="kd"&gt;successfully&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;%OUT%&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;pause&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="nl"&gt;:error&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="kd"&gt;BUILD&lt;/span&gt; &lt;span class="kd"&gt;FAILED&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="kd"&gt;see&lt;/span&gt; &lt;span class="kd"&gt;error&lt;/span&gt; &lt;span class="kd"&gt;above&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;pause&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Anatomy of the Build Command
&lt;/h3&gt;

&lt;p&gt;Taking the &lt;code&gt;ExampleServiceDbContext&lt;/code&gt; line as the example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;dotnet&lt;/span&gt; &lt;span class="kd"&gt;ef&lt;/span&gt; &lt;span class="kd"&gt;migrations&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--project &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%REPO_ROOT%&lt;/span&gt;&lt;span class="s2"&gt;\src\Contoso.AuthService.Api\Contoso.AuthService.Api.csproj"&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;--context &lt;/span&gt;&lt;span class="kd"&gt;ExampleServiceDbContext&lt;/span&gt; &lt;span class="na"&gt;--self-contained -r &lt;/span&gt;&lt;span class="kd"&gt;win&lt;/span&gt;&lt;span class="na"&gt;-x&lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="na"&gt;--configuration &lt;/span&gt;&lt;span class="kd"&gt;Release&lt;/span&gt; &lt;span class="se"&gt;^
&lt;/span&gt;  &lt;span class="na"&gt;-o &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%OUT%&lt;/span&gt;&lt;span class="s2"&gt;\migrate-auth.exe"&lt;/span&gt; &lt;span class="na"&gt;--force
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--project&lt;/code&gt;&lt;/strong&gt;: the &lt;code&gt;.csproj&lt;/code&gt; that contains the &lt;code&gt;DbContext&lt;/code&gt; and its &lt;code&gt;Migrations&lt;/code&gt; folder. In a script that can be invoked from anywhere (not just the project directory), pass this explicitly rather than relying on EF's default of "whatever &lt;code&gt;.csproj&lt;/code&gt; is in the current folder".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--startup-project&lt;/code&gt;&lt;/strong&gt;: a separate flag for the project that acts as the composition root (where the &lt;code&gt;DbContext&lt;/code&gt; actually gets registered in DI and its configuration resolved), when that's a &lt;em&gt;different&lt;/em&gt; project than the one holding the migrations. This shows up the moment you go modular: the &lt;code&gt;Orders&lt;/code&gt;, &lt;code&gt;Billing&lt;/code&gt;, and &lt;code&gt;CommonData&lt;/code&gt; contexts all live in a shared modules library (&lt;code&gt;--project&lt;/code&gt;), but none of them are runnable on their own and they need the actual API host project (&lt;code&gt;--startup-project&lt;/code&gt;) to build a service provider. &lt;code&gt;ExampleServiceDbContext&lt;/code&gt; skips this flag entirely because its context lives directly inside its own API project. Project and startup project are one and the same, so there's nothing to disambiguate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--context&lt;/code&gt;&lt;/strong&gt; : which &lt;code&gt;DbContext&lt;/code&gt; this specific bundle is for (mandatory once a project has more than one).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--self-contained&lt;/code&gt;&lt;/strong&gt;: bakes the matching .NET runtime into the executable itself, so the target machine needs nothing pre-installed. The tradeoff is size: a self-contained bundle is tens of megabytes larger than a framework-dependent one. Worth it for a deploy target you don't fully control or don't want to maintain a runtime version on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-r &amp;lt;RID&amp;gt;&lt;/code&gt;&lt;/strong&gt; : the runtime identifier, and the one flag that matters most for CI/CD. This has to match where the bundle will actually &lt;em&gt;execute&lt;/em&gt;, not where it's built. A &lt;code&gt;win-x64&lt;/code&gt; bundle will not run on a Linux CI runner or inside a Linux container and most default CI/CD images are Linux (GitHub Actions' &lt;code&gt;ubuntu-latest&lt;/code&gt;, GitLab's default runners, Azure DevOps' Linux agent pools). If your pipeline builds and runs the bundle on a Linux agent, you need &lt;code&gt;-r linux-x64&lt;/code&gt; (or &lt;code&gt;linux-musl-x64&lt;/code&gt; specifically for Alpine-based images) regardless of what OS your production database server runs on, the RID describes the machine executing the bundle, not the database. If build and execution happen on different OSes in your pipeline (build on Linux, deploy/run on a Windows box), build for the target the run step will actually use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--configuration Release&lt;/code&gt;&lt;/strong&gt; : builds with Release settings rather than the Debug default, keeping it consistent with the rest of your build pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-o&lt;/code&gt;&lt;/strong&gt; : output path and filename for the resulting executable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--force&lt;/code&gt;&lt;/strong&gt; : overwrites an existing file at the output path instead of erroring because it already exists. Without it, the second and every subsequent run of a CI build step fails on a stale artifact from the previous run, &lt;code&gt;--force&lt;/code&gt; is effectively mandatory for anything that isn't a one-off manual build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few more flags that don't show up above but are worth knowing: &lt;code&gt;--verbose&lt;/code&gt; for full diagnostic output when a build is failing and you can't tell why; &lt;code&gt;--no-build&lt;/code&gt; to skip rebuilding the project first if you've already built it in an earlier pipeline step (saves time in CI); and &lt;code&gt;--framework&lt;/code&gt; if the project multi-targets and you need to pick a specific target framework moniker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;run-all-migrations.bat&lt;/code&gt;&lt;/strong&gt; : runs all four sequentially, tracks failures, and skips gracefully if a bundle wasn't built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;setlocal&lt;/span&gt; &lt;span class="na"&gt;enabledelayedexpansion&lt;/span&gt;
&lt;span class="c"&gt;REM ============================================================================&lt;/span&gt;
&lt;span class="c"&gt;REM run-all-migrations.bat&lt;/span&gt;
&lt;span class="c"&gt;REM&lt;/span&gt;
&lt;span class="c"&gt;REM Runs all four EF Core migration bundles sequentially against their target&lt;/span&gt;
&lt;span class="c"&gt;REM database(s). Safe to run repeatedly - each bundle only applies migrations&lt;/span&gt;
&lt;span class="c"&gt;REM that haven't already been applied (idempotent no-op if already up to date).&lt;/span&gt;
&lt;span class="c"&gt;REM&lt;/span&gt;
&lt;span class="c"&gt;REM Edit the connection strings below to match your target environment.&lt;/span&gt;
&lt;span class="c"&gt;REM ============================================================================&lt;/span&gt;

&lt;span class="c"&gt;REM ExampleServiceDbContext has its own database (matches src\Contoso.ExampleService.Api\appsettings.json).&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"EXAMPLE_CONNECTION=Host=example-db-server;Database=Example;..."&lt;/span&gt;
&lt;span class="c"&gt;REM OrdersDbContext, BillingDbContext and CommonDataDbContext share one Platform&lt;/span&gt;
&lt;span class="c"&gt;REM database (each uses its own schema + migration-history table), matches&lt;/span&gt;
&lt;span class="c"&gt;REM src\Contoso.Platform.Api\appsettings.json.&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"PLATFORM_CONNECTION=Host=platform-db-server;Database=Platform;..."&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="na"&gt;/d &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="vm"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"FAILED_COUNT=0"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="kd"&gt;Contoso&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="kd"&gt;Running&lt;/span&gt; &lt;span class="kd"&gt;all&lt;/span&gt; &lt;span class="kd"&gt;migration&lt;/span&gt; &lt;span class="kd"&gt;bundles&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.

&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="nl"&gt;:run&lt;/span&gt;&lt;span class="na"&gt;-bundle &lt;/span&gt;&lt;span class="s2"&gt;"migrate-auth.exe"&lt;/span&gt; &lt;span class="s2"&gt;"ExampleServiceDbContext"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%EXAMPLE_CONNECTION%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="nl"&gt;:run&lt;/span&gt;&lt;span class="na"&gt;-bundle &lt;/span&gt;&lt;span class="s2"&gt;"migrate-orders.exe"&lt;/span&gt; &lt;span class="s2"&gt;"OrdersDbContext"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%PLATFORM_CONNECTION%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="nl"&gt;:run&lt;/span&gt;&lt;span class="na"&gt;-bundle &lt;/span&gt;&lt;span class="s2"&gt;"migrate-billing.exe"&lt;/span&gt; &lt;span class="s2"&gt;"BillingDbContext"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%PLATFORM_CONNECTION%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="nl"&gt;:run&lt;/span&gt;&lt;span class="na"&gt;-bundle &lt;/span&gt;&lt;span class="s2"&gt;"migrate-common.exe"&lt;/span&gt; &lt;span class="s2"&gt;"CommonDataDbContext"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%PLATFORM_CONNECTION%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;%FAILED_COUNT%&lt;/span&gt; &lt;span class="kd"&gt;EQU&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="kd"&gt;All&lt;/span&gt; &lt;span class="kd"&gt;migration&lt;/span&gt; &lt;span class="kd"&gt;bundles&lt;/span&gt; &lt;span class="kd"&gt;completed&lt;/span&gt; &lt;span class="kd"&gt;successfully&lt;/span&gt;.
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="nv"&gt;%FAILED_COUNT%&lt;/span&gt; &lt;span class="kd"&gt;bundle&lt;/span&gt;&lt;span class="se"&gt;^(&lt;/span&gt;&lt;span class="kd"&gt;s&lt;/span&gt;&lt;span class="se"&gt;^)&lt;/span&gt; &lt;span class="kd"&gt;FAILED&lt;/span&gt;. &lt;span class="kd"&gt;Scroll&lt;/span&gt; &lt;span class="kd"&gt;up&lt;/span&gt; &lt;span class="kd"&gt;to&lt;/span&gt; &lt;span class="kd"&gt;review&lt;/span&gt; &lt;span class="kd"&gt;the&lt;/span&gt; &lt;span class="kd"&gt;errors&lt;/span&gt;.
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;============================================================&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;pause&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="nv"&gt;%FAILED_COUNT%&lt;/span&gt;

&lt;span class="nl"&gt;:run&lt;/span&gt;&lt;span class="na"&gt;-bundle
&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"BUNDLE_EXE=&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="s2"&gt;~1"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"CONTEXT_NAME=&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="s2"&gt;~2"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"CONNECTION=&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="s2"&gt;~3"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;------------------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="nv"&gt;%CONTEXT_NAME%&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;%BUNDLE_EXE%&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;------------------------------------------------------------&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;exist&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%BUNDLE_EXE%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;SKIPPED&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;%BUNDLE_EXE%&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kd"&gt;found&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;this&lt;/span&gt; &lt;span class="kd"&gt;folder&lt;/span&gt;.
    &lt;span class="nb"&gt;echo&lt;/span&gt;.
    &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%BUNDLE_EXE%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="na"&gt;--connection &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%CONNECTION%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;errorlevel&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;%CONTEXT_NAME%&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="kd"&gt;see&lt;/span&gt; &lt;span class="kd"&gt;output&lt;/span&gt; &lt;span class="kd"&gt;above&lt;/span&gt;.
    &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="na"&gt;/a &lt;/span&gt;&lt;span class="kd"&gt;FAILED_COUNT&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kd"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;%CONTEXT_NAME%&lt;/span&gt; &lt;span class="kd"&gt;up&lt;/span&gt; &lt;span class="kd"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;.
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of details in this one worth calling out beyond the obvious "different contexts, different connection strings": the &lt;code&gt;:run-bundle&lt;/code&gt; subroutine skips cleanly (rather than crashing the whole script) if a given &lt;code&gt;.exe&lt;/code&gt; isn't present, and it tracks a running &lt;code&gt;FAILED_COUNT&lt;/code&gt; so the script's own exit code reflects whether &lt;em&gt;anything&lt;/em&gt; failed. Which matters the moment this gets called from a CI step that checks the exit code rather than a human reading the console.&lt;/p&gt;

&lt;p&gt;Don't skip the &lt;code&gt;pause&lt;/code&gt; at the end of either script! Without it the window closes before you get a chance to read whether anything actually happened (see the sections above on misleading red text and hard failures).&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof It Actually Works
&lt;/h2&gt;

&lt;p&gt;To check that everything is correctly applied after running a bundle, I ran &lt;code&gt;dotnet ef database update&lt;/code&gt; from the project directory, pointing it at the same context, project, and startup project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet ef database update `
  --project src\BuildingBlocks\Platform.Modules\Platform.Modules.csproj `
  --startup-project src\Contoso.Platform.Api\Contoso.Platform.Api.csproj `
  --context CommonDataDbContext
Build started...
Build succeeded.
Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
No migrations were applied. The database is already up to date.
Done.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same &lt;code&gt;--project&lt;/code&gt;/&lt;code&gt;--startup-project&lt;/code&gt;/&lt;code&gt;--context&lt;/code&gt; flags as the bundle build, they're general &lt;code&gt;dotnet-ef&lt;/code&gt; flags, not something specific to bundling. And the result is the confirmation: the bundle already applied everything there was to apply, so the CLI tool finds nothing left to do and reports the database as current.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bundles vs. the Alternatives
&lt;/h2&gt;

&lt;p&gt;EF Core gives you three real ways to get migrations onto a database. Here's how they stack up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Runtime (&lt;code&gt;MigrateAsync&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;SQL Script&lt;/th&gt;
&lt;th&gt;Migration Bundle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Safe with multiple app instances&lt;/td&gt;
&lt;td&gt;Only on EF Core 9+ (via locking)&lt;/td&gt;
&lt;td&gt;Yes (manual process)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs elevated DB permissions on the app itself&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No (only the bundle run needs them)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspectable before it runs&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, fully&lt;/td&gt;
&lt;td&gt;Only the source migrations, not the compiled artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires SDK/source on target machine&lt;/td&gt;
&lt;td&gt;Yes (it's the app)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effort to set up&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;Highest (manual DBA step)&lt;/td&gt;
&lt;td&gt;Low once scripted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Runtime migration is the simplest but carries every risk above. SQL scripts give a DBA full visibility and control but add a manual step. Bundles sit in between: no source code or dev tooling required on the target, still a single deliberate execution, still inspectable before you ship it, and they slot in cleanly as a first-class step in a deploy pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where They Actually Fit
&lt;/h2&gt;

&lt;p&gt;CI/CD and production pipelines are the first things that come to mind. A bundle is a natural artifact to produce in a build pipeline and hand off to a deploy step, and it deals cleanly with stale schemas without needing the app itself to have schema-altering permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bundles trade the simplicity of runtime migration for a single, auditable, out-of-process execution. It's worth it the moment more than one instance or more than one service touches the database.&lt;/li&gt;
&lt;li&gt;One bundle per &lt;code&gt;DbContext&lt;/code&gt;. Plan scripts around this from the start.&lt;/li&gt;
&lt;li&gt;A red "no migrations applied" log line is not a failure. Trust the final &lt;code&gt;Done.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appsettings.json&lt;/code&gt; next to the bundle needs the right &lt;em&gt;keys&lt;/em&gt;, not real values. &lt;code&gt;--connection&lt;/code&gt; overrides the actual connection string. If the file's missing entirely, though, that's a hard failure, so make sure yours is structured correctly too.&lt;/li&gt;
&lt;li&gt;In CI/CD, &lt;code&gt;-r&lt;/code&gt; has to match the OS actually &lt;em&gt;running&lt;/em&gt; the bundle, not your production server. Most default pipeline runners are Linux, so build &lt;code&gt;linux-x64&lt;/code&gt; unless you know the execution step happens on Windows. And use &lt;code&gt;--force&lt;/code&gt;, or your second pipeline run fails on a stale artifact.&lt;/li&gt;
&lt;li&gt;They're idempotent, so wiring them into every deploy unconditionally is safe.&lt;/li&gt;
&lt;li&gt;For test fixtures with a disposable container per run, &lt;code&gt;Migrate()&lt;/code&gt;/&lt;code&gt;EnsureCreated()&lt;/code&gt; beats a bundle since the app's already in-process. Save the bundle itself for one dedicated CI step that validates the exact artifact you're about to ship.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Documentation:&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/dotnet/efcore/issues/27685" rel="noopener noreferrer"&gt;https://github.com/dotnet/efcore/issues/27685&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
