Mit den Migrations vom Entity Framework Core ist ein Update der Datenbank sehr leicht durchführbar. Die Fallback-Routine in Form der Down-Methode kann im Falle eines Downgrades als Fangnetz für unterwartete Probleme dienen. Bei einfachen Anwendungsszenarien klappt das recht gut. Doch was für Varianten bestehen, wenn mit der Schema-Migration auch Datenmigrationen durchgeführt werden müssen?
Problem
Wie schaffe ich die Grundlagen, um im Falle eines Release-Rollback den Ursprungszustand der Datenbank wiederherstellen zu können, ohne aufwändige Migrationsroutinen für die Down-Methode im Entity Framework für Datenmigrationen schreiben zu müssen?
Aktion
Als erstes betrachteten wir die möglichen Varianten eines Backups wie:
- SQL Server Import and Export Wizard
- SSIS Tools
- Export Data-tier Application
- SqlPackage Utility
- BCP Utility
- SqlBackupAndFtp
- Azure Portal
Dabei orientierten wir uns am Artikel How to backup Azure SQL Databases to Local Machine.
Da wir ein Backup innerhalb der Azure DevOps Pipeline anlegen wollten, fiel unser Entscheid auf das SqlPackage Utility.
Azure DevOps stellt dafür den Task Azure SQL Database Deployment bereit. Den Einsatz dieser Komponente verwarfen wir jedoch, da hier die Verbindungsangaben zur Datenbank hinterlegt werden müssen. Unser bestehendes Konzept setzt auf einen OnPremise Build Server mit Umgebungsvariablen, sodass wir weder im Git-Repository noch in der Build Pipeline Zugangsdaten für die Datenbanken hinterlegen. Dieses Konzept wollten wir beibehalten. Aus diesem Grund setzten wir auf den Ansatz mittels Kommandozeile.
sqlpackage.exe /Action:Export /ssn:«server» /sdn:«datenbank» /su:«user» /sp:«password» /tf:«datenbank».bacpac
Den Aufruf platzierten wir vor der Ausführung der Migrations in einem Kommandozeilen-Programm, welches die Verbindungsparameter aus den Umgebungsvariablen liest.
Das Beispiel in C#:
Console.WriteLine("Backup Database"); var con = new SqlConnectionStringBuilder(o.Connectionstring); var path = Environment.CurrentDirectory; Console.WriteLine("Zielverzeichnis {0}", path); var sqlPackage = Path.Combine(path, "SqlPackage", "SqlPackage.exe"); var arguments = $"/Action:Export /ssn:{con.DataSource} /sdn:{con.InitialCatalog} /su:{con.UserID} /sp:{con.Password} /tf:{Path.Combine(path, con.InitialCatalog + "_" + o.Name + ".bacpac")}"; using (var process = new Process()) { process.StartInfo.FileName = sqlPackage; process.StartInfo.Arguments = arguments; process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.OutputDataReceived += (sender, data) => Console.WriteLine(data.Data); process.ErrorDataReceived += (sender, data) => Console.WriteLine(data.Data); Console.WriteLine("Starte Backup"); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); var exited = process.WaitForExit(20000); Console.WriteLine($"Backup beendet {exited}"); }
Der Aufruf der Komponente erfolgt bereits in der Pipeline, bei der Ausführung der Entity Framework Migrations, sodass keine weiteren Anpassungen nötig sind.
Resultat
Statt einer aufwändigen Routine für das Erstellen der Down-Methode einer Datenmigration kann so das Datenbank-Backup wiederhergestellt werden, wenn ein Release-Rollback nötig ist. Die Datenbank lässt sich als Nebeneffekt auch auf einen lokalen SQL Server einspielen.
Im Artefakt-Folder stehen die Backups für einen Ernstfall bereit.
Hinweis: Unser Beispiel der .NET User Group Bern arbeitet mit öffentlichen Veranstaltungsdaten. So haben wir das Daten-Backup im Artefakt-Folder abgelegt. Bei Daten anderer Klassifizierungsstufen kann dies mit Qualitätskriterien bzw. Sicherheitsaspekten der jeweiligen Geschäftsdomäne in Konflikt stehen.