Since the last lesson, every migration runs in a stage of its own, before the new version is rolled out. That has a consequence that is easy to overlook: between the migration and the end of the rollout, the old version is still working, and it is already working with the new schema. Container Apps starts the new revision, switches the traffic over when it is ready, and only then deactivates the old one. With the traffic split from lesson 3.7.6, the two even run side by side for as long as you like. And when you roll back to the previous revision, the old version runs again, against a schema it has never seen.

That gives us a single rule for this lesson:

The schema of a release must work with the code of this release and with the code of the previous release.

What that means in detail, you will see in a real restructuring of the Kladde: a contact's company changes from a text field to a table of its own.

What the previous version can tolerate

EF Core writes the columns explicitly into every SELECT and every INSERT, never SELECT *. A new column therefore does not bother old code. Everything that old code still uses, on the other hand, must not disappear or change its name.

Change Can the previous version tolerate it? How to do it without downtime
Create a table yes in one step
Create a column that allows NULL or has a default value yes in one step
Create an index yes in one step; on large tables with ONLINE = ON so the table stays writable
Create a column as NOT NULL without a default value no, its INSERT does not know the column create it with NULL first, fill it, and make it NOT NULL in the next release
Rename a column no, it still asks for the old name new column, both write, migrate, drop the old column later
Change the type of a column or shorten it no, its values may not fit anymore same as renaming
Drop a column or table no, as long as it reads or writes it first roll out the code that no longer knows it, then drop

The three bottom rows have the same recipe, known as expand and contract (also called parallel change):

  1. Expand: The new thing is added, the old thing stays. The code writes both and reads the new thing first, otherwise the old one.
  2. Migrate: The old data moves into the new thing. The code reads and writes only the new thing; the old thing is still in the table, but no code knows it anymore.
  3. Contract: The old thing is dropped.

Every step is a pull request of its own and a release of its own. Each can be rolled out on its own, and from each you can roll back one step.