Entity Framework Core w aplikacji konsolowej .NET 6 — pierwsza migracja

Konfiguracja EF Core w aplikacji konsolowej .NET 6 — pakiety NuGet, DbContext, connection string i pierwsza migracja code-first. Krok po kroku.

JAJarosław Armatys 7 min czytania
Wersja wideo tego wpisu — obejrzyj na YouTube

Cześć! To drugi odcinek serii, w której budujemy WP Importera — aplikację konsolową w .NET 6, która ciągnie dane z Google Places API i wrzuca je jako wpisy do WordPressa. W poprzednim odcinku rozłożyliśmy plan na czynniki pierwsze, a dziś bierzemy się za fundament całej zabawy: podłączenie do bazy danych przez Entity Framework Core.

Cel na dziś jest prosty — po skończeniu tego wpisu będziesz mieć aplikację konsolową, która łączy się z lokalną bazą SQL Server, ma zdefiniowany model Place, skonfigurowany DbContext, a schemat bazy powstaje w podejściu code-first przez migracje (Add-Migration, Update-Database).

Jeśli nie wiesz jak założyć projekt konsolowy w .NET 6 — w opisie filmu (i w repo na GitHubie) masz wszystkie linki. Tu zakładam, że masz pustego solution z jednym projektem typu Console App (.NET 6).

Instalacja pakietów NuGet

Zaczynamy od dorzucenia EF Core. W aplikacji konsolowej musimy to zrobić ręcznie — nie mamy szablonu webowego, który by nam to podrzucił. Klikasz prawym na projekcie → Manage NuGet Packages i instalujesz dwie paczki:

  • Microsoft.EntityFrameworkCore — sam silnik EF w wersji 6.x (pod .NET 6),
  • Microsoft.EntityFrameworkCore.SqlServer — dostawca dla SQL Server (bez tego EF nie wie, jakim dialektem gadać z bazą),
  • Microsoft.EntityFrameworkCore.Tools — bez tego nie odpalisz Add-Migration ani Update-Database z Package Manager Console.

Jak później okaże się, że czegoś nie ma (np. Microsoft.Extensions.Configuration.Json, żeby czytać appsettings.json) — dorzucisz kolejne paczki w locie. Na start te trzy wystarczą.

Konwencja, której się trzymam: wersje paczek EF Core dopasowuję do wersji .NET. Dla .NET 6 — EF Core 6.x. Dla .NET 7 — 7.x. Brzmi banalnie, ale widziałem zbyt wiele projektów, gdzie ktoś przez przypadek ściągnął EF 8 na .NET 6 i potem się dziwił.

Model — Place

Zanim podłączymy się do bazy, potrzebujemy czegoś, co EF będzie mapował. W folderze Models (jak go nie masz — dodaj) tworzymy klasę, która reprezentuje pojedynczy rekord. W naszym importerze będzie to Place — miejsce pobrane z Google Places API, które potem trafi jako post do WordPressa.

public class Place
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public double Rating { get; set; }
}

Cztery właściwości i tyle. Klucz Id rozpoznawany jest przez EF po konwencji nazwy — nie musisz niczego dopisywać ani wklepywać atrybutu [Key]. To samo dotyczy typów — string mapuje się na nvarchar, double na float, int na int.

W serii dorzucimy później więcej pól i powiążemy Place z innymi bytami (województwo, branża, forma prawna), ale na dziś świadomie trzymamy model możliwie płaski — chcę, żebyś najpierw zobaczył, jak działa migracja na najprostszym przypadku.

DbContext i connection string

Najważniejsza klasa w EF to DbContext. To ona wie, jakie encje są zmapowane, jak połączyć się z bazą i jak śledzić zmiany. Tworzę sobie folder Database i wrzucam do niego plik ImporterDbContext.cs:

public class ImporterDbContext : DbContext
{
    public DbSet<Place> Places { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=WpImporter");
}

Co się tu dzieje:

  • DbSet<Place> Places — to „wirtualna tabela" w kodzie. Nazwę w liczbie mnogiej dobieram świadomie, bo EF po konwencji nazwie tabelę tak samo (Places), a w bazie zwykle chcemy liczby mnogiej.
  • OnConfiguring — tu wstrzykujemy connection string. Używam LocalDB, czyli lekkiej instancji SQL Servera, która leci razem z Visual Studio — nie musisz instalować pełnego SQL Server Developer Edition, żeby się pobawić. mssqllocaldb to domyślna nazwa instancji.

Na produkcji connection string nigdy nie ląduje hardkodem w DbContext — trafia do appsettings.json (albo zmiennych środowiskowych) i wstrzykujemy go przez DbContextOptions w konstruktorze. W kolejnych odcinkach serii przeniesiemy go właśnie do konfiguracji, ale na teraz świadomie zostawiam w OnConfiguring — mniej ruchomych części, więcej widać, co EF robi.

Package Manager Console i pierwsza migracja

Teraz najciekawsza część — tworzymy bazę bez otwierania SQL Server Management Studio. Cały schemat powstanie z klas C#.

W Visual Studio otwieramy Tools → NuGet Package Manager → Package Manager Console i odpalamy:

Add-Migration Init
Update-Database

Co robi każda z tych komend:

  • Add-Migration Init — EF porównuje aktualny model (Place, ImporterDbContext) z ostatnią znaną migracją (a że żadnej jeszcze nie ma — porównuje z „niczym"). Efekt: generuje plik w folderze Migrations, w którym w metodzie Up() jest CreateTable dla Places, a w Down() — DropTable. Init to tylko nazwa migracji — może być cokolwiek, ale dobrze zacząć od czegoś opisowego.
  • Update-Database — EF łączy się bazą podaną w connection stringu, sprawdza tabelę __EFMigrationsHistory (którą sam sobie stworzy, jak jej nie ma) i wykonuje wszystkie migracje, których tam nie zarejestrowano. Czyli: tworzy bazę WpImporter (bo jej jeszcze nie było), tworzy tabelę Places i zapisuje w historii, że migracja Init poszła.

Po tych dwóch komendach w SQL Server Object Explorer (View → SQL Server Object Explorer) zobaczysz bazę WpImporter z tabelą Places oraz __EFMigrationsHistory. Gotowe — masz działającą bazę, której schematu nie napisałeś ręcznie.

Dlaczego w ogóle robimy code-first, a nie database-first? Bo schemat bazy wtedy jest częścią repozytorium. Każda zmiana modelu (nowe pole, nowa tabela, nowy indeks) kończy się kolejną migracją, którą commitujesz razem z kodem. Reviewer widzi, co się zmieniło w bazie. CI/CD wie, co odpalić na środowisku. Zero ręcznego klikania w SSMS.

Testujemy czy działa

Żeby zobaczyć, że faktycznie gadamy z bazą, w Program.cs możemy zrobić szybki odczyt. Wrzucam ręcznie kilka rekordów przez SSMS (albo skryptem seed — o tym w kolejnym odcinku) i wołam:

using var db = new ImporterDbContext();
var places = await db.Places.ToListAsync();
Console.WriteLine($"Znaleziono {places.Count} miejsc.");

Odpalamy F5, stawiamy breakpoint na ToListAsync(), sprawdzamy, czy lista wraca z bazy. Jak wraca — gratulacje, masz skonfigurowany EF Core w aplikacji konsolowej .NET 6.

Typowe problemy na tym etapie

Zebrałem listę rzeczy, na których ludzie się wykładają najczęściej — warto je znać, zanim wylądujesz na Stack Overflow z płaczem:

  • „Unable to create an object of type 'ImporterDbContext'" przy Add-Migration — EF Core Tools potrzebuje albo bezparametrowego konstruktora, albo implementacji IDesignTimeDbContextFactory<T>. W naszej wersji mamy OnConfiguring, więc działa out-of-the-box.
  • Brak Microsoft.EntityFrameworkCore.Tools — komendy Add-Migration po prostu nie istnieją. Dodaj paczkę i uruchom PMC od nowa.
  • Connection string wskazuje na instancję, której nie ma — (localdb)\\mssqllocaldb zadziała na maszynie z Visual Studio. Na macOS/Linuxie trzeba odpalić SQL Server w Dockerze albo użyć SQLite.
  • Plik appsettings.json nie kopiuje się do bin/ — gdy przeniesiesz connection string do configa, kliknij prawym na pliku → Properties → Copy to Output Directory: Copy if newer. Bez tego aplikacja po zbudowaniu nie znajdzie configa.

Co dalej?

Mamy pierwszy kawałek układanki. Baza stoi, DbContext gada z SQL Serverem, model Place jest zmapowany, a schemat powstaje z kodu. W kolejnym odcinku:

  • przeniesiemy connection string z OnConfiguring do appsettings.json,
  • skonfigurujemy Dependency Injection w aplikacji konsolowej (IServiceCollection + IHost), żeby nie tworzyć DbContext ręcznie przez new,
  • podłączymy się do Google Places API, żeby zacząć ciągnąć realne dane.

Cały kod z tego odcinka (tag Lekcja-1) znajdziesz tu: https://github.com/jarmatys/dotnet-wordpress-importer

Jeśli masz pytania albo utknąłeś na którymś z kroków — wrzuć komentarz pod filmem, odpisuję na każdy. Do zobaczenia w kolejnym odcinku!

Tagi:#course-dotnet-wordpress#dotnet#entity-framework#csharp

Masz pytanie do tego wpisu?

Napisz do mnie — chętnie porozmawiam o szczegółach technicznych, albo o tym jak zastosować to u Ciebie w projekcie.