When writing LoECDA.com, we (#LoECDA) wanted to build the site using modern technologies implementing best DevOps practices. LoECDA.com is written using asp.net core, running in a Docker container, connected to a SQL Server database, all hosted in Azure with a full DevOps pipeline implemented in VSTS. Each developer check in can flow into our different environments automatically. Potentially, it can even flow all the way to production with no human intervention except for approval gates! Managing database changes in this DevOps world can be challenging and the way we solved it for this specific project was by using Entity Framework Code-First.
In EF Code-First, you define your database schema by simply defining your domain model using plain old c# objects. EF takes care of the rest for you. When you change the domain object model, you generate a migration. A migration is c# code that is run against your database, dynamically changing the schema of your database to match your object model when your app starts up. Perfect right? Database DevOps done easy for developers!
Well…. It should have been perfect, except I messed up in spectacular fashion.
- I created the app and on app starup, I made the call
context.EnsureCreated(). When the app was deployed for the first time and the app started up, it noticed there wasn’t a database so it created a new database for me with the schema that matched my domain objects. Hooray for that!
- I made some changes to my object model and thought, “Oh man I forgot to turn on migrations!” So in Visual Studio, I pulled up the Package Manager Console and typed in enable-migrations. This created the Migrations folder in my project. Next I typed in add-migration InitialCreate which created my first migration in my Migrations folder. Running my app locally caused a failure but I wasn’t really paying attention and I just thought, oh, since I just turned on migrations, things must be out of sync. I’ll just drop my db, start everything back up and we’ll be in sync. Made my changes, pushed to dev and prod, dropped the db’s (all the while thinking wow, I really messed up, I shouldn’t ever drop my DB. Talk about bad DevOps practices!!!) and all looked good.
- I Needed to make another change to my model. Made my changes, created another migration using add-migration , ran it locally and realized my huge mistake. Instead of context.EnsureCreated(), which just creates the database but doesn’t save the migrations in the __EFMigrationsHistory table, I needed to call context.Migrate().
- Changing my code to context.Migrate() also broke things because now it wanted to run my first migration (since according to the __EFMigrationsHistory table, no migrations have been run), which tries to create tables that already existed!!!!
- So to work around the problem, I added code in my first migration to not run if the tables were already in place.
Whew! Painful. Moral of the story. Think about how you will be changing and pushing database changes out from the start! Also, if you are using EF with Code-First migrations and you want your schema to auto migrate on startup, use context.Migrate() NOT context.EnsureCreated()