Przyszedł moment, w którym nasza aplikacja z kursu przestaje być tylko "widokiem z formularzem", a zaczyna trzymać dane w bazie. W tym wpisie przechodzimy krok po kroku przez konfigurację Entity Framework Core w ASP.NET Core MVC — od instalacji paczek, przez DbContext, aż po pierwszą migrację i zapis produktu do SQL Servera.
Jeśli wolisz zerknąć od razu na kod, cały projekt do tej lekcji masz tutaj: github.com/jarmatys/asp-net-core-podstawy (Lekcja-6).
Po co Entity Framework
Mógłbyś pisać zapytania SQL ręcznie, ClassLibrary z SqlConnection, SqlCommand, ręczne mapowanie wyników na obiekty… i po tygodniu mieć dość. EF Core to ORM — pozwala Ci myśleć o danych w kategoriach klas i obiektów, a nie tabel i kursorów.
Dorzucasz model Product, mówisz "to jest tabela", odpalasz migrację i EF Core sam generuje CREATE TABLE, sam wyciąga dane, sam aktualizuje. Oczywiście to nie jest magia — pod spodem i tak leci SQL, tylko Ty go nie piszesz. W zamian dostajesz:
- mapowanie modeli na tabele (konwencja nad konfiguracją),
- migracje, czyli wersjonowanie schematu bazy,
- LINQ jako sposób pisania zapytań,
- śledzenie zmian (change tracking) — nie musisz pamiętać, co zmieniłeś, EF to ogarnia.
W naszym kursie używam EF Core 3.1, bo cała aplikacja leci na .NET Core 3.1. Jeśli startujesz od zera w nowszym .NET, weź najnowszą wersję EF Core zgodną z Twoim targetem — składnia, którą tu pokazuję, jest kompatybilna.
Pakiety NuGet i connection string
Zaczynamy od trzech paczek. W Visual Studio otwieramy Manage NuGet Packages for Solution i szukamy entity:
Microsoft.EntityFrameworkCore— core biblioteki,Microsoft.EntityFrameworkCore.Tools— to dzięki temu pakietowi działają komendyAdd-MigrationiUpdate-Databasew Package Manager Console,Microsoft.EntityFrameworkCore.SqlServer— provider pod SQL Server (będziemy jechać na LocalDB).
Wszystkie w tej samej wersji, zgodnej z Twoim .NET. U mnie w kursie to 3.1.13.
Teraz connection string. Wrzucamy go do appsettings.json, żeby nie trzymać go w kodzie:
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=KursDb;Trusted_Connection=True"
}
}
Dwa ukośniki \\ to nie przypadek — w JSON-ie musisz je zescape'ować. Jeśli dostaniesz ostrzeżenie od Visual Studio o znakach specjalnych w stringu w kodzie C#, użyj @"..." (verbatim string) — wtedy nie musisz eskejpować backslasha.
LocalDB jest wygodny, bo jedzie lokalnie na Twoim laptopie bez stawiania pełnego SQL Servera. Do nauki i do małych projektów w zupełności wystarcza.
DbContext i DbSet
DbContext to serce EF Core. To klasa, przez którą rozmawiasz z bazą. Każda właściwość typu DbSet<T> odpowiada jednej tabeli.
Robimy katalog Data (albo u mnie w kursie bezpośrednio w projekcie) i dodajemy klasę AppDbContext:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
}
Co tu się dzieje:
- dziedziczymy po
DbContextzMicrosoft.EntityFrameworkCore, - konstruktor przyjmuje
DbContextOptions<AppDbContext>i przekazuje do klasy bazowej — dzięki temu za chwilę wStartup.cswstrzykniemy tu connection string i provider, DbSet<Product> Products— ta nazwa (Products) stanie się nazwą tabeli w bazie. EF Core pluralizuje nazwy modeli konwencją, więc jeżeli Twoja klasa toProduct, DbSet o nazwieProductsda Ci tabelęProducts. Proste.
W modelu Product wypada jeszcze oznaczyć klucz główny. Jeśli masz właściwość Id, EF Core rozpozna ją automatycznie, ale dla porządku często dodaję atrybut jawnie:
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Category { get; set; }
}
[Key] wymaga using System.ComponentModel.DataAnnotations;. Id typu int staje się autoinkrementowalnym kluczem — bazie wygenerujemy go przy pierwszym Save.
Jeden
DbContextper logiczna baza danych. W kursie mamy jeden, ale w większych projektach czasem widuje się dzielenie kontekstów per moduł. Na start trzymaj się jednego — łatwiej debugować migracje.
Teraz rejestracja w Startup.cs, metoda ConfigureServices:
// Startup.ConfigureServices
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
AddDbContext wrzuca AppDbContext do kontenera DI ze scopem Scoped (jeden per request HTTP). UseSqlServer to metoda z paczki Microsoft.EntityFrameworkCore.SqlServer — mówi EF-owi, że gadamy z SQL Serverem. Configuration.GetConnectionString("DefaultConnection") czyta sekcję ConnectionStrings:DefaultConnection z appsettings.json.
Od tego momentu każdy kontroler i serwis może sobie w konstruktorze poprosić o AppDbContext i EF go dostarczy.
Migracje i aktualizacja bazy
Schemat bazy powstaje z Twoich modeli. Ale ktoś musi go wygenerować — to robią migracje.
Otwierasz Package Manager Console (View → Other Windows → Package Manager Console), upewniasz się, że Default Project to Twój projekt z AppDbContext, i odpalasz:
Add-Migration Init
Update-Database
Add-Migration Init:
- skanuje Twój
DbContexti wszystkieDbSet<T>, - generuje folder
Migrations/z klasąYYYYMMDDhhmmss_Init.cs, - w tej klasie znajdziesz metody
Up()(co zrobić przy aplikacji migracji) iDown()(jak cofnąć — przydatne przy rewertach), - w
Up()zobaczyszmigrationBuilder.CreateTable("Products", ...)z kolumnami odpowiadającymi Twojemu modelowi.
Migracja to tylko plan. Baza wciąż nie istnieje. Do jej utworzenia i zastosowania migracji służy:
Update-Database — EF Core łączy się z SQL Serverem po connection stringu, jeżeli baza KursDb nie istnieje, to ją utworzy, a potem odpala Up() wszystkich niezaaplikowanych migracji.
Po udanym Update-Database odśwież SQL Server Object Explorer w Visual Studio. Zobaczysz:
- tabelę
Productsz kolumnamiId,Name,Description,Category, - tabelę
__EFMigrationsHistory— tu EF zapisuje, które migracje już zaaplikował. Dzięki temuUpdate-Databasenie próbuje tej samej migracji wrzucić dwa razy.
Nie edytuj plików migracji ręcznie, chyba że wiesz co robisz. Jeżeli zmienisz model, odpalasz kolejną migrację (
Add-Migration DodaniePola) i znowuUpdate-Database. EF policzy diff i dopisze migrację inkrementalnie.
Pierwszy zapis i odczyt
Mamy bazę, mamy kontekst, mamy tabelę. Czas coś zapisać. W serwisie, który wstrzykujemy do kontrolera (u nas WarehouseService), bierzemy AppDbContext przez konstruktor:
public class WarehouseService : IWarehouseService
{
private readonly AppDbContext _dbContext;
public WarehouseService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public int AddProduct(Product product)
{
_dbContext.Products.Add(product);
return _dbContext.SaveChanges();
}
public List<Product> GetProducts()
{
return _dbContext.Products.ToList();
}
}
Co warto wiedzieć:
_dbContext.Products.Add(product)tylko oznacza obiekt jako "do wstawienia". SQL jeszcze nie leci.SaveChanges()dopiero wysyłaINSERTdo bazy. Zwraca liczbę zmodyfikowanych rekordów — przydatne do sprawdzenia, czy zapis się udał.- Po
SaveChanges()EF Core aktualizuje obiektproducto wartośćIdwygenerowaną przez bazę. Więc jeżeli wołaszAddProduct(product), to po wyjściu z metodyproduct.Idbędzie już miał właściwą wartość — nie trzeba robić dodatkowegoSELECT. ToList()materializuje zapytanie. Póki piszesz_dbContext.Products.Where(...), to jeszczeIQueryable— zapytanie nie poszło. DopieroToList(),FirstOrDefault()alboforeachje odpala.
W kontrolerze, po walidacji ModelState.IsValid, wywołujesz _warehouseService.AddProduct(product) i gotowe — masz produkt w bazie, a zwrócony Id możesz pokazać userowi albo użyć do redirectu.
Co dalej?
Masz już pełny pipeline: model → DbContext → migracja → baza → zapis → odczyt. To jest fundament, na którym zbudujesz całą warstwę danych aplikacji. W kolejnej lekcji kursu wchodzimy głębiej w EF Core — pobieranie pojedynczego rekordu po Id, filtrowanie, aktualizacje, usuwanie. Tam się zaczyna prawdziwa zabawa z LINQ.
Kod do tej lekcji: github.com/jarmatys/asp-net-core-podstawy (Lekcja-6) — sklonuj, odpal Update-Database i zobacz, jak to działa u siebie. Jak coś nie pyka, zostaw komentarz pod filmem albo napisz przez armatys.me — rozgryziemy.



