We all strive to keep stable development branches in a clean state. Fresh checkouts should never have broken code, failing tests, or anything else that could make deployments or starting new tasks difficult. Pull request reviews, automated builds, checks, tests, and more all contribute to enforce this standard. A lot of effort is put into making sure the clean branch does not become dirty. One instance creating a distinct lack of cleanliness I’ve come across occasionally is a dirty database schema state.
A Simple Example
You get latest from your stable branch, assuming all is well. Of course it compiles, the tests are all passing, etc. You happily go about your task, adding a column to a table in the database. You and your team are using Entity Framework Code First Migrations, so it’s a simple matter of updating your entity class, and running a command to generate the new migration.
What’s this? Why is your migration touching tables that have nothing to do with your change? Whoops! Looks like a previous task resulted in an unintentional database schema update.
Your stable branch isn’t in a clean state. Oh no! Now your team has to retroactively create the database migration and make sure everything works as intended still. This doesn’t have to be painful (creating a fresh branch dedicated to this fix). Or maybe it is, since it was an unintentional schema update, and the previous task has to be re-examined to make sure it didn’t create bugs by side effect. Either way, now you’re context switching and the task you were working on is delayed.
Could this have been prevented? Do we have a tool in our automated toolbelt we can use to prevent this state from occurring within a stable development branch? Yep!
Checking for Pending Model Changes
In addition to the commands for generating migrations and applying them, Entity Framework comes equipped with a command to merely check for the existence of model changes in your application code: has-pending-model-changes
. Added in EF Core 8.0, this feature will inform on whether pending changes exist.
dotnet ef migrations has-pending-model-changes -c MyDbContext
Assuming the application builds successfully, this command yields one of a couple results:
No changes have been made to the model since the last migration.
You’re all set! Nothing has changed in your application model. Running a database migration generation command will yield an empty migration.Changes have been made to the model since the last migration. Add a new migration.
Changes detected! Generating a migration will create a migration that will change the database schema in some way.
Note that the second message shows up in scary red letters, to really make sure you see it! In an environment where I’m not 100% certain the model can be in a clean state, or if I’m just curious if a change I made indirectly impacts my schema, I’ll run this command to verify. It’s more convenient than running the migration generation command, inspecting the resulting migration to see if it did anything, then undoing the changes it made.
Automating the Check for Pending Model Changes
Of course, running this on a whim won’t protect the stable branch on its own. We would much rather make sure that nobody can merge changes into the stable branch without first verifying their changes do not incur schema changes that don’t have accompanying database migrations.
Like most other commands, you can script it out in your automated build to run, and fail your build if changes are detected.
GitHub Action
Let’s add a build step to our GitHub Action that runs against pull requests to validate no pending migrations exist. Note that the command effectively returns a boolean value for our purposes. If it returns false, then changes exist and the build should fail.
- name: Install dotnet-ef
run: dotnet tool install --global dotnet-ef
- name: Check MyDbContext Migrations
run: |
if ! dotnet ef migrations has-pending-model-changes -c MyDbContext; then
echo "MyDbContext has pending model changes that require a new database migration."
exit 1
fi
We install dotnet-ef
, then run the command. If it detects changes, we fail the build (exit 1
) so that the pull request author is informed, and can make the necessary migration. Our stable branch now remains clean!
Automated Test
Entity Framework exposes this check in code as well. A simple test can be written, and since tests are run during builds, a failure here would achieve the same goal at preventing dirty states from entering our stable branch.
[Fact]
public void AssureNoPendingModelChanges()
{
// Creating an instance of our database context for the test.
// (Beyond the scope of this blog post.)
MyDbContext context = GetMyDbContext();
// Run the check.
bool hasPendingChanges = context.Database.HasPendingModelChanges();
hasPendingChanges.ShouldBeTrue();
}
(ShouldBeTrue()
is a method from the Shouldly library.)
The Stable Branch Now Remains Clean
With either method in place, there’s now one less attack vector that can muddy a branch that should always be clean. It’s good practice to make sure whatever database schema changes you’re making are properly documented. When using Code First migrations, this comes in the form of code, generating a migration script. Now, with has-pending-model-changes
available, it can be easily enforced, and automated checks never forget to run!