Devops
Add EF Core Migration Checks to CI

Add EF Core Migration Checks to CI

Have you ever had a deploy succeed but then your website error because the code didn't match the database? With Entity Framework Core using migrations this had started to become a thing of the past. The database model is defined in code and with a setup which checks for migrations needing to be applied, the database was always kept up to date. However this is exactly what has happened twice in the last few months in one of our dev environments, and the reason is AI.

EF Core Migrations are scripts that are generated based on your code models. A developer makes code changes to a model, runs the add migration script and the next time apply migrations is run (either through the app startup or another process), the migrations get applied to the DB.

1dotnet ef migrations add AddBlogCreatedTimestamp

However, if an AI tool like Copilot or Claude is being used by the developer you can run into a scenario where a migration script is attempted to be written by the AI, but isn't quite right. In our case it didn't add the .design file.

Now one solution to this is to update your AGENTS.md file to include instructions specifying that your migrations folder should only be updated via the dotnet ef migrations tools, but it would still be advantageous to have your CI tests detect the error and automatically fail the status checks on your PRs. After all this could happen for many other reasons like the changes being missed from a git commit.

Automatically detect model / migration inconsistencies

Fortunately the migration tools have a command to do this exact thing.

1dotnet ef migrations has-pending-model-changes

The has-pending-model-changes command will compare your migration code with the model and return true if changes exist.

If you already have unit tests in your code this can easily be added as a test.

1using MyApp.Infrastructure;
2using Microsoft.EntityFrameworkCore;
3using Npgsql;
4
5namespace Tests;
6
7public class HasEfModelChangesTests
8{
9 [Fact]
10 public async Task TestHasEfModelChanges()
11 {
12 await using var dataSource = CreateDataSource();
13 await using var dbContext = CreateDbContext(dataSource);
14
15 var hasChanges = dbContext.Database.HasPendingModelChanges();
16
17 Assert.False(hasChanges, "The EF model has pending changes. Add a new migration.");
18 }
19
20 // Mirrors the Program.cs setup so the model matches production. No connection is opened.
21 private static NpgsqlDataSource CreateDataSource()
22 {
23 var builder = new NpgsqlDataSourceBuilder("Host=localhost;Database=model_check");
24 builder.EnableDynamicJson();
25 builder.UseVector();
26 return builder.Build();
27 }
28
29 private static AppDbContext CreateDbContext(NpgsqlDataSource dataSource)
30 {
31 var options = new DbContextOptionsBuilder<AppDbContext>()
32 .UseNpgsql(dataSource, o => o.UseVector())
33 .Options;
34
35 return new AppDbContext(options);
36 }
37}
38

One thing to note in my example is that I am using Npgsql rather than an in memory database. Typically for unit tests I'd be using an in memory db and set the on model creating function to ignore any tables not relevant for the test.

In this case we are testing the whole model schema and my model includes data types such as jsonb and pgvector which aren't supported, so instead npgsql is used. No actual DB is required and the connection string is just a dummy connection string, it's just used to access the model building functionality.

Auto Format C# Code

Auto Format C# Code

When you're reading code there are a few things which make the process a lot easier, particularly when it was code written by other people.

  1. It's commented well.
  2. It's written in a logical way.
  3. Lines of text aren't really really long meaning you have to side-scroll.
  4. The formatting is consistent.

There's not a lot we can do about the first two other than having a good team, but the second two are something that can be automated and enforced.

In many languages (particularly front-end code) there's a tool named prettier that can configure everything from tab spacing, line length and even if you end a line with a semi-colon or not. I've used it for years and it's brilliant. Unfortunately, it doesn't support C#.

A tool I have found though is CSharpier (https://csharpier.com/), and like Prettier it is an opinionated code formatter.

Add CSharpier to your project

To get started with CSharpier first create a tool-manifest file in the root of your project and then run the install command.

You can do this with the following terminal commands.

1# if you don't yet have a .config/dotnet-tools.json file
2dotnet new tool-manifest
3
4dotnet tool install csharpier

Next add a configuration file to your project so that all team members will produce the same results.

To do this add a JSON file named .csharpierrc.json to the root of the solution.

1{
2 "printWidth": 100,
3 "useTabs": false,
4 "tabWidth": 4,
5 "endOfLine": "auto"
6}

After you add CSharpier you will want to reformat every file in one go, otherwise your going to spend a year going through PRs mostly containing format changes as every file gradually gets reformated.

Do this with the following command.

1dotnet csharpier .

Configure Visual Studio to auto format on save

There's lots of options for triggering the format; You can do it manually, on Pre-commit hooks, or within CI tools, but I find the best is to have a file format on save. This way not only is it making your code readable to everyone else, it also makes it easier to read as your working on it. Plus when tab spacing doesn't fix itself correctly its a good indication there's something wrong with your code.

To do this you will need to install the Visual Studio extension https://marketplace.visualstudio.com/items?itemName=csharpier.CSharpier

Once installed you then need to configure it to format on save. This is located under Tools | Options | CSharpier | General and can either be configured at a global and project level.

CSharpier Visual Studio Config Dialog

And that's it. Now whenever you save a file it will automatically get reformated.

Handy default Netlify toml file settings

Handy default Netlify toml file settings

With Netlify it's quite easy to quickly spin up a new website with a lot of good default out the box, but there's a few options that are good to set for both security and seo reasons.

Firstly it's good practice to have some headers set to improve security. For example setting X-Content-Type-Options to nosniff will disable sniffing of mime types. Setting X-Frame-Options to DENY will prevent the website being rendered in a frame on another site and Referrer-Policy of same-origin will reduce the amount of referrer information sent to other websites.

It's also a good idea to setup a redirect on the Netlify subdomain that Netlify will have created for your site. If you don't, then you could find the wrong URL ending up in Google.

1[[headers]]
2 for = "/*"
3 [headers.values]
4 Referrer-Policy = "same-origin"
5 Strict-Transport-Security = "max-age=15768000"
6 X-Content-Type-Options = "nosniff"
7 X-Frame-Options = "DENY"
8 X-XSS-Protection = "1; mode=block"
9[[redirects]]
10 from = "https://example.netlify.app"
11 to = "https://example.com/"
12 status = 301
13 force = true
How to setup CI with Azure App Service using GitHub Actions and deployment slots

How to setup CI with Azure App Service using GitHub Actions and deployment slots

Today I'm going to go through how you can setup automatic deployments when using a combination of Azure App Service (e.g. a Web App), deployment slots on the App Service and GitHub Actions. This is a setup to achieve zero downtime.

What are GitHub Actions?

GitHub Actions are the continuous integration and continuous delivery (CI/CD) platform from GitHub. If you're working with the Microsoft tech stack you may have used tools such as Team City or Azure Dev Opps in the past and this is just GitHubs version. Like the newer Azure Devops pipelines GitHub Actions define the pipeline in yaml files and while they are not the same they are very similar.

What are App Service Deployment Slots?

Deployment slots solve two issues; Firstly they allow zero downtime deployments by deploying the application to a separate instance to production, warming up that instance and then swapping it. Secondly, they can also be used as separate environments to preview changes without affecting the production code.

One thing to note is that although they are separate from the production code, they do share the same resources. So if your change has a processor-hungry bug, then that will have an affect your live environment. For this reason its also best to not leave them running all the time.

Configure App Service Deployment Slows

As a pre-requisite you need an App Service configured in Azure. Once you have this head to the Deployment slots section in the left nav.

Click the Add Slot button, and create a slot named staging.

Create Azure Deployment Slot

Auto Swap Slots

At this point you could setup a deployment pipeline that deploys to the staging slot and then performs a slot swap, to change which one is in production. However Azure has a nifty setting that will do this all for you.

Click into the Deployment slot you just created and then go to Configuration, in the left-hand nav.

Switch to the General settings tab.

App Service - Configuration (General Settings tab)

Scroll down to a section called Deployment Slot, and turn on auto swap. Within the drop-down select production.

Auto Deployment Swap setting

Now when a deployment is made to the staging slot it will warm up and then automatically swap with the production slot.

Configure GitHub Action

To configure the GitHub Action first make sure you are still in the staging deployment slot and select Deployment Center in the left nav.

From the source drop-down select GitHub.

Deployment Center - Selecting Source

Next select the Organization, Repository and branch to deploy for. This will give you the option to add a workflow. You may need to edit this file to get the build to work as needed but, this will provide the basics for building a .NET application and configuring the publish profile to get it into Azure.

Deployment Center - configure GitHub Action