EF Core uruchamianie migracji w Azure DevOps

Wprowadzenie

Kilka miesięcy temu w artykule o uruchamianiu migracji Entity Framework w Azure DevOps pokazałem jak utworzyć aplikacje konsolową i za jej pomocą migrować schemat bazy danych podczas wdrażania nowej wersji aplikacji hostowanej w Azure App Service. Tamten wpis dotyczył starego Entity Framework. Dostawałem od Was pytania o aktualizację wpisu dla Entity Framework Core, co czynię w poniższym wpisie.

Migrator

W tym wpisie skupie się bardziej na różnicach w stosunku do poprzedniego przykładu. Dlatego w pierwszej kolejności zachęcam do przeczytania tamtego artykułu – Uruchamianie migracji bazy danych w Azure DevOps.

Pominę również kwestię tworzenia snapshotu bazy danych, którą opisałem w poprzednim wpisie. Ta kwestia tutaj ewentualnie zostaje bez zmiany i tak naprawdę jest tylko potrzebna w momencie, gdy chcesz wykonywać automatyczne testy Postmana, które kiedyś opisywałem.

Podobnie jak w tamtym przykładzie, wykorzystam bibliotekę CommandLinePaser do przekazania connection stringa do migratora. Użyłem tylko jeden parametr w klasie Options:

public class Options
{
[Option('c', "connectionString", Required = true, HelpText = "The connection string to database that needs to be updated.")]
public string ConnectionString { get; set; }
}
view raw Options.cs hosted with ❤ by GitHub

Natomiast kod samego migratora zmienił się praktycznie w całości. Po sparsowaniu przekazanych parametrów w pierwszej kolejności konfigurujemy instancję klasy ServiceProvider, która posłuży nam do utworzenia obiektu DbContext z Entity Framework Core.

private static ServiceProvider GetServiceCollection(Options options)
{
var serviceCollection = new ServiceCollection();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
{"ConnectionStrings:DefaultConnection", options.ConnectionString}
})
.Build();
serviceCollection.AddSingleton<IConfiguration>(config);
var startup = new Startup(config);
startup.ConfigureServices(serviceCollection);
return serviceCollection.BuildServiceProvider();
}

Pierwszym obiektem jaki zarejestrujemy jest obiekt konfiguracji aplikacji. Jest nam to potrzebne, abyśmy mogli przekazać connection stringa do bazy danych. W tym przypadku skorzystamy tylko z źródła w pamięci aplikacji, aby dodać tylko connection stringa do bazy. W przypadku innych parametrów, które nie muszą być przekazywane do migratora, możemy użyć plików json, jak to jest robione w normalnej aplikacji.

Pozostałą konfigurację ServiceProvidera robimy za pomocą metody ConfigureService z klasy Startup głównego projektu. Metoda ta zarejestruje obiekt DbContext, który za chwilkę wykorzystamy.

Samo wykonanie migracji jest dość proste. Tworzymy instancje klasy DbContext z wykorzystaniem service providera. Następnie na właściwości Database wywołujemy metodę Migrate:

private static void Migrate(Options options)
{
var serviceProvider = GetServiceCollection(options);
var context = serviceProvider.GetRequiredService<DataContext>();
Console.WriteLine("Migration is in progress...");
Console.WriteLine("All:");
Console.WriteLine(FormatMigrations(context.Database.GetMigrations()));
Console.WriteLine("Applied:");
Console.WriteLine(FormatMigrations(context.Database.GetAppliedMigrations()));
Console.WriteLine("Pending:");
Console.WriteLine(FormatMigrations(context.Database.GetPendingMigrations()));
Console.WriteLine("Migrating...");
context.Database.Migrate();
Console.WriteLine("Database has been migrated");
}
view raw Migrate.cs hosted with ❤ by GitHub

Przed wywołaniem metody Migrate na konsoli wyświetlam informacje o wszystkich migracjach z aplikacji, migracjach, które zostały wykonane oraz tych które wykona migrator.

Cały kod klasy Program z migratora:

class Program
{
static void Main(string[] args)
{
var result = Parser.Default.ParseArguments<Options>(args);
result
.WithParsed(Migrate);
}
private static void Migrate(Options options)
{
var serviceProvider = GetServiceCollection(options);
var context = serviceProvider.GetRequiredService<DataContext>();
Console.WriteLine("Migration is in progress...");
Console.WriteLine("All:");
Console.WriteLine(FormatMigrations(context.Database.GetMigrations()));
Console.WriteLine("Applied:");
Console.WriteLine(FormatMigrations(context.Database.GetAppliedMigrations()));
Console.WriteLine("Pending:");
Console.WriteLine(FormatMigrations(context.Database.GetPendingMigrations()));
Console.WriteLine("Migrating...");
context.Database.Migrate();
Console.WriteLine("Database has been migrated");
}
private static string FormatMigrations(IEnumerable<string> migrations)
{
if (migrations.Any() == false)
{
return "\tNone";
}
return string.Join(Environment.NewLine, migrations.Select(m => $"\t{m}"));
}
private static ServiceProvider GetServiceCollection(Options options)
{
var serviceCollection = new ServiceCollection();
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>()
{
{"ConnectionStrings:DefaultConnection", options.ConnectionString}
})
.Build();
serviceCollection.AddSingleton<IConfiguration>(config);
var startup = new Startup(config);
startup.ConfigureServices(serviceCollection);
return serviceCollection.BuildServiceProvider();
}
}
view raw Program.cs hosted with ❤ by GitHub

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Uruchomienie migratora w Visual Studio

Podobnie jak we wcześniejszych przykładach możemy w ustawieniach projektu w Visual Studio ustawić domyślny connection string, który użyje migrator podczas uruchamiania aplikacji w VS. Wystarczy, że przejdziemy do ustawień projektu oraz do zakładki Debug i tam ustawimy wartość parametru -c w „Application arguments”:

ef core migration debug settings

Po uruchomieniu migratora w efekcie uzyskamy coś takiego:

ef core migration migrator from vs

Tutaj już wcześniej wszystkie migracje zostały wykonane na lokalnej bazie danych.

Uruchomienie migracji w Azure DevOps

Uruchomienie migracji w Azure DevOps będzie bardzo podobne do poprzedniego wpisu. Główną różnicą będzie sposób budowania migratora. Task, który to robi to:

Wykonujemy tutaj komendę dotnet publish na projekcie migratora. Bardzo istotne jest tutaj przekazanie parametrów „–self-contained true -r win10-x64”, które spowodują, że efekcie otrzymamy plik exe migratora, która później łatwiej będzie uruchomić podczas wdrażania aplikacji.

Cała zawartość pliku azure-pipelines.yml, którą użyłem w przykładzie możesz znaleźć na githubie – https://github.com/danielplawgo/EFCoreMigrations/blob/master/azure-pipelines.yml

Sama konfiguracja release w Azure DevOps jest taka sama jak w poprzednim wpisie. Czyli mamy dwa kroki. Pierwszy uruchamia migratora, do którego przekazujemy connection stringa z zmiennej. A drugi to wrzucenie nowej wersji do Azure App Service:

ef core migration azure devops release definition

Problem z buildem migratora

Podczasu budowy migratora w Azure DevOps możecie spotkać się z błędem:

Bazując na opisie błędu na githubie https://github.com/dotnet/sdk/issues/10566 można to obejść ustawiając w referencji do projektu w webowego właściwość GlobalPropertiesToRemove ustawioną na SelfContained . Tak jak to widać poniżej:

<ItemGroup>
<ProjectReference Include="..\EFCoreMigrations.Web\EFCoreMigrations.Web.csproj" GlobalPropertiesToRemove="SelfContained" />
</ItemGroup>

To powinno rozwiązać problem z budowaniem migratora w Azure DevOps.

Przykład

Tradycyjnie przykład znajduje się na githubie – https://github.com/danielplawgo/EFCoreMigrations. Po jego pobraniu należy ustawić poprawnego connection stringa w ustawieniach projektu, o których wspomniałem w treści wpisu.

Podsumowanie

Jak widać w przypadku Entity Framework Core możemy również łatwo przygotować prostą aplikację konsolową, która wykona migrację bazy danych podczas wdrażania nowej wersji. Z czasem możną tą aplikację rozbudować o jakieś dodatkowe funkcjonalności. Tak jak tworzenie snapshotów bazy danych lub wypełnianie bazy testowymi danymi.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *