Intro
I have had an opportunity to work with Octopus Deploy at my current workplace and it's quite fascinating to me how easy and intuitive it is to use. We have had several discussions among the team members about how we have managed to solve some of our complex deployment needs in short amount of time using Octopus Deploy.
In this blog post, I will cover how we can automate code first migrations using Entity Framework Code First to various environments in conjunction with Octopus Deploy. I will not go into details about some more complex situations that may arise, for example, different applications and projects using the same database along with the same NuGet package to roll out Entity Framework Code First Migrations.
Technology Stack Used
- Entity Framework 6+ (Code First)
- SQL Server
- NuGet Repository (although this isn't needed, you will see later why and how this makes the entire process so much easier).
- Octopus Deploy
Entity Framework Code First
First and foremost, we need to make sure that our Entity Framework Code First files are contained within a separate solution or project. If you haven't done this already, please do so now so that later steps will be easier to manage. Personally, I prefer to have EF DbContext in a completely separate solution that I can publish to a private NuGet feed.
-
We need to include migrate.exe tool that comes with NuGet package for Entity Framework 6.0+. If you're not familiar with this tool, please spend some time now to get familiar with arguments that you may want to supply for your needs before we move on to the next step.
-
Next, we need to tell our .nuspec file that we want to copy specific files so that it is bundled with our NuGet package. Below is an example of a nuspec file that I like to use. Obviously, we have hardcoded a path to the version of Entity Framework but you could also just include the migrate.exe file as part of the solution.
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>Database.Name</id> <version>$version$</version> <title>Database.Name</title> <authors>Malav Dhalgara</authors> <owners>Malav Dhalgara</owners> <licenseUrl>https://www.dhalgara.com</licenseUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>This package provides DbContext used by Entity Framework as well as migrations.</description> <releaseNotes>Initial release.</releaseNotes> <copyright>Copyright 2018</copyright> <language>en-US</language> <tags>Database Name Entity Framework</tags> </metadata> <files> <file src="..\packages\EntityFramework.6.2.0\tools\migrate.exe" /> <file src="..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll" /> <file src="..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll" /> </files> </package>
-
Finally, create and verify that the NuGet package for this solution/project has the assembly that contains DbContext as well as migrate.exe that we will use in the next step.
Octopus Deploy
Now that we've got the Entity Framework Code First work out of the way, let's begin by making changes to Octopus Deploy to be able to perform automated migrations.
Create a step template
-
Create a new Built-in Step Template in Octopus Deploy that can run a PowerShell script.
-
Provide appropriate name, logo, and description so it's meaningful (see screenshot below).
-
Create parameters that we will use within Octopus Deploy project. These parameters will get passed into this Step Template and will be used in the Deployment script.
-
In the Step, make sure that you select appropriate NuGet Package Feed. You will likely only see one option if you haven't installed or configured your own internal NuGet feed. Nevertheless, Octopus Deploy gives you an option to use a built-in Package Feed if that's what you prefer. The package Id as shown in the screenshot below is the Id used in the nuspec file above.
Next, provide the following Powershell script under the Deployment script section. I have included some debugging information to make sure that we can locate both the assembly as well as a config file to be able to perform migrations.
$assemblyString = $($AssemblyDllName) $assembly = $($assemblyString) + ".dll" $assemblyExists = $(Test-Path $($assembly)) $assemblyConfigExists = $(Test-Path ($($assembly) + ".config")) Write-Host ("Assembly is " + $($assembly)) Write-Host ("Assembly Exists?: " + $($assemblyExists)) Write-Host ("Assembly Config Exists?: " + $($assemblyConfigExists)) if ($assemblyExists -and $assemblyConfigExists){ if ($Target_Migration -eq "ALL"){ # Applying all possible migrations $migrateCommand = "& .\migrate.exe $assembly /connectionString=""$DatabaseConnectionString"" /connectionProviderName=System.Data.SqlClient /startUpConfigurationFile=$assembly.config /verbose" Write-Host ("Executing: " + $migrateCommand) Invoke-Expression $migrateCommand | Write-Host } elseif ($Target_Migration -eq "NONE"){ # Skipping all migrations Write-Host ("Skipping migrations...") }else{ # Applying specific migration as provided $migrateCommand = "& .\migrate.exe $assembly /connectionString=""$DatabaseConnectionString"" /connectionProviderName=System.Data.SqlClient /startUpConfigurationFile=$assembly.config /targetMigration=$Target_Migration /verbose" Write-Host ("Executing: " + $migrateCommand) Write-Host Invoke-Expression $migrateCommand | Write-Host } } else{ Write-Error "Either the assembly or assembly configuration file is missing." }
Note that I am expecting a total of three parameters to be passed into this PowerShell script. These are the same parameters that we created in the previous step and hopefully are self-explanatory to you. The third parameter called Target Migration indicates which migration you'd like to apply. The script is expecting you to provide either ALL, NONE, or MIGRATION_ID.
Integrate Database Migration Step Template in Project
Finally, we are now ready to integrate this custom Step Template to use in our Deployment Project within Octopus Deploy. Fortunately, this is easy as adding a new step in the process and referencing our custom Step Template and providing appropriate values to the three parameters we created earlier.