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 odpaliszAdd-MigrationaniUpdate-Databasez 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ć.mssqllocaldbto domyślna nazwa instancji.
Na produkcji connection string nigdy nie ląduje hardkodem w
DbContext— trafia doappsettings.json(albo zmiennych środowiskowych) i wstrzykujemy go przezDbContextOptionsw konstruktorze. W kolejnych odcinkach serii przeniesiemy go właśnie do konfiguracji, ale na teraz świadomie zostawiam wOnConfiguring— 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 folderzeMigrations, w którym w metodzieUp()jestCreateTabledlaPlaces, a wDown()—DropTable.Initto 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ęPlacesi zapisuje w historii, że migracjaInitposzł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 implementacjiIDesignTimeDbContextFactory<T>. W naszej wersji mamyOnConfiguring, więc działa out-of-the-box. - Brak
Microsoft.EntityFrameworkCore.Tools— komendyAdd-Migrationpo prostu nie istnieją. Dodaj paczkę i uruchom PMC od nowa. - Connection string wskazuje na instancję, której nie ma —
(localdb)\\mssqllocaldbzadziała na maszynie z Visual Studio. Na macOS/Linuxie trzeba odpalić SQL Server w Dockerze albo użyć SQLite. - Plik
appsettings.jsonnie kopiuje się dobin/— 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
OnConfiguringdoappsettings.json, - skonfigurujemy Dependency Injection w aplikacji konsolowej (
IServiceCollection+IHost), żeby nie tworzyćDbContextręcznie przeznew, - 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!



