Start learning free

LESSON 80 OF 173 · BACKEND: APIS WITH A DATABASE

Change the Table: Migrations in C#

Practice

The shop wants to give products a discount, and the table products has no column for it. A change to the structure of the tables is a migration. ALTER TABLE changes a table that already holds rows, and a DEFAULT fills the new column in for every one of them.

A migration must happen exactly once: the app starts again and again, and a database that already has the column must not get it twice. Some statements are safe to repeat, others are not (Execute is your helper from placing orders):

Execute("CREATE TABLE IF NOT EXISTS notes (text TEXT NOT NULL)");  // the 2nd time: does nothing
Execute("ALTER TABLE customers ADD COLUMN phone TEXT");  // the 2nd time: error, duplicate column name

So the database keeps a small table of its own, schema_version, with one row per change made. When the app starts, it looks for the row of each change: if the row is missing, it makes the change and records it. Two check routes help: /check/schema shows the columns of products and the versions recorded, and /check/migrate-again runs Migrate() once more, as the next start of the app would.

GET /products/1  →  200  {"id": 1, "name": "Mug", "price": 12, "category": "kitchen", "stock": 10, "discount": 0}
GET /check/schema  →  200  {"columns": ["id", "name", "price", "category", "stock", "discount"], "versions": [1]}
GET /check/migrate-again  →  200  {"columns": ["id", "name", "price", "category", "stock", "discount"], "versions": [1]}
POST /products  {"name": "Vase", "price": 18, "category": "home"}  →  201  {"id": 9}
GET /products/9  →  200  {"id": 9, "name": "Vase", "price": 18, "category": "home", "stock": 0, "discount": 0}
GET /products?sort=price&order=desc&per_page=2  →  200  {"products": [{"id": 8, "name": "Gift card", …, "discount": 0}, {"id": 3, "name": "Lamp", …, "discount": 0}], "total": 9, "next_page": 2}
GET /check/migrate-again  →  200  {"columns": [… the same 6 …], "versions": [1]}

Your task: write a local function Migrate() and call it when the app starts. It creates schema_version if needed and, only if version 1 isn't recorded yet, adds the column discount (a whole number, 0 by default) to products and records version 1.


Assignment
Follow the Coding Tutorial to write Migrate(), call it at start-up, and add the discount column exactly once.


Hint
ExecuteScalar() answers null when no row matches: that is your sign that change 1 wasn't made yet. Record the version inside the same if as the ALTER TABLE. There is no commit to write: with no transaction open, each statement is saved as soon as it runs.


STEP 1 OF 7
    Ctrl + Enter to check

    Db Migration in C#

    Keep going with C#1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free