wersja 1
https://learn.microsoft.com/en-us/ef/core/cli/dotnet https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding
Konfiguracja połączenia z bazą danych w Entity Framework Core opiera się na współpracy kilku kluczowych elementów projektu. Najpierw, w pliku appsettings.json, umieszcza się ciąg połączenia (connection string), który zawiera wszystkie informacje potrzebne do nawiązania komunikacji z bazą danych, takie jak adres serwera, nazwa bazy, dane logowania oraz dodatkowe parametry konfiguracyjne. Ten plik powinien znajdować się w głównym katalogu projektu (obok pliku Program.cs) i jest automatycznie odczytywany podczas uruchamiania aplikacji.
Konfiguracja może być przechowywana w domyślnych plikach i źródłach, które są odczytywane w następującej kolejności:
Jeśli poszczególne źródła konfiguracji modyfikują ten sam parametr, zostanie użyta wartość z tego źródła, które zostanie wczytane najpóźniej – czyli ma wyższy priorytet.
appsettings.json{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=YourDatabaseName;Trusted_Connection=True;"
}
}
Kolejnym krokiem jest utworzenie klasy kontekstu, dziedziczącej po DbContext. Klasa ta, zwykle nazywana AppDbContext lub podobnie, znajduje się zazwyczaj w folderze Data lub Infrastructure, aby zachować porządek w projekcie. W tej klasie definiuje się właściwości typu DbSet
using Microsoft.EntityFrameworkCore;
using YourProject.Models;
namespace YourProject.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
// (opcjonalnie) konfiguracje modeli przez Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Aby połączyć te elementy, w pliku Program.cs (lub Startup.cs w starszych wersjach .NET) należy zarejestrować kontekst w kontenerze zależności za pomocą builder.Services.AddDbContext<>(), przekazując do niego odpowiednią metodę dostawcy bazy danych, np. UseSqlServer() w przypadku SQL Servera. W tej metodzie wykorzystuje się wcześniej zdefiniowany ciąg połączenia odczytany przez Configuration.GetConnectionString(“nazwa”). Aby narzędzia wiersza poleceń EF Core (dotnet ef migrations, dotnet ef database update) działały poprawnie w aplikacjach nie opartych o ASP.NET Core (jak Windows Forms, WPF itp.), należy utworzyć fabrykę kontekstu (Design-Time DbContext Factory). Dzięki niej EF Core wie, jak utworzyć kontekst podczas operacji projektowych.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
// Fabryka kontekstu używana podczas operacji projektowych (np. migracji).
// Dzięki niej Entity Framework Core wie, jak utworzyć DbContext,
// nawet gdy aplikacja nie korzysta z ASP.NET Core (np. Windows Forms).
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
// Metoda wywoływana przez EF Core w czasie projektowym (design-time),
// np. podczas wykonywania poleceń migracji.
public AppDbContext CreateDbContext(string[] args)
{
// Tworzenie konfiguracji na podstawie pliku appsettings.json.
// Directory.GetCurrentDirectory() zapewnia poprawną ścieżkę podczas działania z CLI.
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // Ustawienie katalogu bazowego (ważne przy uruchamianiu z linii komend)
.AddJsonFile("appsettings.json") // Dodanie pliku z ustawieniami
.Build(); // Zbudowanie obiektu konfiguracji
// Tworzymy obiekt opcji dla DbContext na podstawie connection stringa.
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
// Konfigurujemy DbContext do używania SQL Servera (można zmienić na np. UseSqlite).
optionsBuilder.UseSqlServer(connectionString);
// Zwracamy nową instancję kontekstu z odpowiednio skonfigurowanymi opcjami.
return new AppDbContext(optionsBuilder.Options);
}
}
Cały ten proces zapewnia modularność i elastyczność – pozwala zmieniać bazę danych bez ingerencji w logikę aplikacji, a dzięki wykorzystaniu dependency injection kontekst EF jest bezproblemowo dostępny w kontrolerach, serwisach czy repozytoriach. Dodatkowo, jeśli projekt wykorzystuje migracje, po zakończeniu konfiguracji można wygodnie zarządzać strukturą bazy komendami dotnet ef migrations add i dotnet ef database update.
Klasa modelowa w Entity Framework to zwykła klasa C#, która odwzorowuje strukturę tabeli w bazie danych – jej właściwości odpowiadają kolumnom, a relacje między klasami odzwierciedlają powiązania między tabelami (np. jeden-do-wielu). Klasy modelowe powinny być umieszczane w folderze Models, w osobnych plikach nazwanych zgodnie z nazwą klasy, czyli w liczbie pojedynczej i z użyciem notacji PascalCase (np. Product.cs, Customer.cs). Dzięki temu projekt jest czytelny, łatwy do utrzymania i zgodny z przyjętymi konwencjami. Modele te są następnie rejestrowane w klasie kontekstu DbContext, gdzie każda z nich powinna mieć przypisany DbSet<T>, co umożliwia EF automatyczne tworzenie i obsługę odpowiadających im tabel. W razie potrzeby klasy można dodatkowo opisywać adnotacjami, takimi jak [Key], [Required], [MaxLength], czy [ForeignKey], aby precyzyjnie kontrolować mapowanie do bazy danych i walidację danych na poziomie modelu.
| Adnotacja | Co robi | Użycie (gdzie stosować) |
|---|---|---|
[Key] |
Ustawia pole jako klucz główny (PRIMARY KEY) | Właściwość |
[Required] |
Pole nie może być NULL | Właściwość |
[MaxLength(n)] |
Maksymalna długość ciągu znaków (np. dla nvarchar(n)) | Właściwość |
[MinLength(n)] |
Minimalna długość ciągu znaków (głównie walidacja w ASP.NET) | Właściwość |
[StringLength(max)] |
Określa maksymalną i opcjonalnie minimalną długość | Właściwość |
[Range(min, max)] |
Ogranicza wartość liczbową do danego zakresu | Właściwość |
[RegularExpression("regex")] |
Waliduje tekst zgodnie z wyrażeniem regularnym | Właściwość |
[Column("Nazwa", TypeName = "typ_sql")] |
Ustawia nazwę i typ kolumny w bazie danych (np. nvarchar(100)) |
Właściwość |
[Table("NazwaTabeli")] |
Ustawia nazwę tabeli (gdy inna niż nazwa klasy) | Klasa |
[NotMapped] |
Właściwość nie zostanie zmapowana do bazy danych | Właściwość |
[DatabaseGenerated(DatabaseGeneratedOption.None)] |
Użytkownik musi sam wypełnić pole | Właściwość |
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] |
Wartość generowana automatycznie (np. AUTO_INCREMENT) | Właściwość |
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] |
Generowane przez SQL (np. GETDATE()) | Właściwość |
[ForeignKey("NazwaWłaściwości")] |
Definiuje właściwość jako klucz obcy | Właściwość |
[InverseProperty("WłaściwośćZDrugiejStrony")] |
Określa odwrotną nawigację w relacji (np. 1:N) | Właściwość |
[DataType(DataType.X)] |
Ustawia typ danych (pomocne w formularzach: Email, Phone, Date, Url itd.) | Właściwość |
[EmailAddress] |
Waliduje poprawność adresu e-mail | Właściwość |
[Phone] |
Waliduje numer telefonu | Właściwość |
[Url] |
Waliduje URL | Właściwość |
[Display(Name = "NazwaWyświetlana")] |
Zmienia etykietę w formularzach i widokach | Właściwość |
[DefaultValue("SQL_Wyrażenie")] |
Wartość domyślna kolumny (np. GETDATE()) – działa głównie z Fluent API |
Właściwość |
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace YourProject.Models
{
public class Product
{
[Key] // Klucz główny
public int Id { get; set; }
[Required] // Wymagane pole
[MaxLength(100)] // Maksymalna długość tekstu
public string Name { get; set; }
[Column(TypeName = "decimal(10,2)")] // Typ danych w bazie
public decimal Price { get; set; }
[DataType(DataType.Date)]
public DateTime CreatedAt { get; set; } = DateTime.Now;
// Relacja do innej tabeli (wielu do jednego)
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public Category Category { get; set; }
}
}
Wyświetlenie listy migraacji
dotnet ef migrations list
Dodanie pierwszej migracji na podstawie klas modelowych
dotnet ef migrations add Init
Dodanie migracji o danej nazwie
dotnet ef migrations add <Migration_name>
public partial class AddNewTablesAndIndexes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Tworzenie nowej tabeli 'Categories'
migrationBuilder.CreateTable(
name: "Categories", // Nazwa tabeli
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"), // Klucz główny z autoinkrementacją
Name = table.Column<string>(nullable: false) // Kolumna 'Name' nie może być null
},
constraints: table =>
{
// Określenie klucza głównego w tabeli 'Categories'
table.PrimaryKey("PK_Categories", x => x.Id); // Ustawienie 'Id' jako klucz główny
});
// Tworzenie nowej tabeli 'Customers'
migrationBuilder.CreateTable(
name: "Customers", // Nazwa tabeli
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"), // Klucz główny z autoinkrementacją
FullName = table.Column<string>(nullable: false), // Kolumna 'FullName' nie może być null
Email = table.Column<string>(nullable: true) // Kolumna 'Email' może być null
},
constraints: table =>
{
// Określenie klucza głównego w tabeli 'Customers'
table.PrimaryKey("PK_Customers", x => x.Id); // Ustawienie 'Id' jako klucz główny
});
// Dodanie nowej kolumny 'CategoryId' do tabeli 'Products'
// Kolumna ta będzie wskazywała na tabelę 'Categories' (relacja z kluczem obcym)
migrationBuilder.AddColumn<int>(
name: "CategoryId", // Nazwa kolumny
table: "Products", // Tabela, do której dodajemy kolumnę
nullable: true); // Kolumna może mieć wartość null (może nie mieć przypisanej kategorii)
// Tworzenie indeksu na kolumnie 'CategoryId' w tabeli 'Products'
// Indeks przyspiesza operacje wyszukiwania, sortowania i filtracji po tej kolumnie
migrationBuilder.CreateIndex(
name: "IX_Products_CategoryId", // Nazwa indeksu
table: "Products", // Tabela, na której tworzymy indeks
column: "CategoryId"); // Kolumna, dla której tworzymy indeks
// Tworzenie klucza obcego między tabelą 'Products' a tabelą 'Categories'
// Zapewnia to powiązanie produktu z kategorią, w której jest przypisany
migrationBuilder.AddForeignKey(
name: "FK_Products_Categories_CategoryId", // Nazwa klucza obcego
table: "Products", // Tabela, do której dodajemy klucz obcy
column: "CategoryId", // Kolumna, która będzie kluczem obcym
principalTable: "Categories", // Tabela, do której klucz obcy się odnosi
principalColumn: "Id", // Kolumna, do której klucz obcy się odnosi (klucz główny)
onDelete: ReferentialAction.SetNull); // Zachowanie przy usuwaniu - usunięcie kategorii ustawi null w CategoryId
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Usuwanie klucza obcego z tabeli 'Products'
// Zdejmuje powiązanie między produktem a kategorią
migrationBuilder.DropForeignKey(
name: "FK_Products_Categories_CategoryId",
table: "Products");
// Usuwanie indeksu z tabeli 'Products' na kolumnie 'CategoryId'
// Zwalnia miejsce, usuwając indeks na tej kolumnie
migrationBuilder.DropIndex(
name: "IX_Products_CategoryId",
table: "Products");
// Usuwanie kolumny 'CategoryId' z tabeli 'Products'
// Kolumna ta była kluczem obcym wskazującym na tabelę 'Categories'
migrationBuilder.DropColumn(
name: "CategoryId",
table: "Products");
// Usuwanie tabeli 'Customers'
// Całkowite usunięcie tabeli zawierającej dane klientów
migrationBuilder.DropTable(
name: "Customers");
// Usuwanie tabeli 'Categories'
// Całkowite usunięcie tabeli zawierającej dane kategorii
migrationBuilder.DropTable(
name: "Categories");
}
}
Usunięcie migracji
dotnet ef migrations remove
Usuwa bazę danych
dotnet ef database drop
Wykonanie migracji (wywołanie metody up)
dotnet ef database update <Nazwa migracji>
Cofanie wszystkich migracji (wywołanie metody down)
dotnet ef database update 0
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class UserService
{
private readonly AppDbContext _db;
public UserService(AppDbContext db)
{
_db = db;
}
// CREATE
public async Task CreateUserAsync(User user)
{
_db.Users.Add(user);
await _db.SaveChangesAsync();
}
// READ ALL
public async Task<List<User>> GetAllUsersAsync()
{
return await _db.Users.ToListAsync();
}
// READ BY ID
public async Task<User> GetUserByIdAsync(int id)
{
return await _db.Users.FindAsync(id);
}
// READ CHUNKS (np. do paginacji)
public async Task<List<User>> GetUsersPagedAsync(int pageNumber, int pageSize)
{
return await _db.Users
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
// FILTERING (np. użytkownicy powyżej 30 roku życia)
public async Task<List<User>> GetUsersFilteredAsync(int minAge)
{
return await _db.Users
.Where(u => u.Age >= minAge)
.ToListAsync();
}
// SORTING (np. po imieniu rosnąco)
public async Task<List<User>> GetUsersSortedByNameAsync()
{
return await _db.Users
.OrderBy(u => u.Name)
.ToListAsync();
}
// UPDATE
public async Task UpdateUserAsync(User updatedUser)
{
var existing = await _db.Users.FindAsync(updatedUser.Id);
if (existing == null) return;
existing.Name = updatedUser.Name;
existing.Email = updatedUser.Email;
existing.Age = updatedUser.Age;
await _db.SaveChangesAsync();
}
// DELETE
public async Task DeleteUserAsync(int id)
{
var user = await _db.Users.FindAsync(id);
if (user != null)
{
_db.Users.Remove(user);
await _db.SaveChangesAsync();
}
}
}
| Element | Czym jest? | Kiedy używać? |
|---|---|---|
| Form | Okno aplikacji | Każdy osobny widok, np. MainForm, LoginForm, ProductsView |
| MDI Parent Form | Główne okno, które “hostuje” inne formularze (MdiChildren) |
Gdy chcesz jedno okno z dynamicznie ładowanymi widokami |
| User Control | Część UI, którą możesz wielokrotnie osadzać w formularzach | Gdy masz komponent wielokrotnego użycia, np. ProductCard, AddressPanel |
| Custom Control | Kontrolka napisana od zera lub rozszerzona (np. własny przycisk) | Gdy potrzebujesz bardziej zaawansowaną, własną kontrolkę, np. RoundedButton, StarRating |
| AboutBox | Wbudowany szablon formularza “O programie” (z nazwą, wersją, itd.) | Tylko do wyświetlania informacji o programie (wersja, autor itp.) |
Utwórz projekt Windows Forms w Visual Studio. Użyj NuGet Package Manager, aby zainstalować Microsoft.EntityFrameworkCore oraz Microsoft.EntityFrameworkCore.SqlServer (lub inny provider np. Microsoft.EntityFrameworkCore.Sqlite). Zaprojektuj model danych: Utwórz klasy modelu User, Product, Order i dodaj odpowiednie atrybuty ([Key], [Required], [MaxLength] itd.). Utwórz klasę AppDbContext, która dziedziczy po DbContext, i dodaj DbSet dla swoich modeli. Skonfiguruj połączenie z bazą danych w App.config (lub appsettings.json) i utwórz connection string.
W terminalu użyj komendy dotnet ef migrations add InitialCreate, aby stworzyć migrację na podstawie swoich modeli. Użyj komendy dotnet ef database update, aby utworzyć tabele w bazie danych. Zaktualizuj modele (np. dodaj nowe właściwości) i ponownie utwórz migrację oraz zaktualizuj bazę danych. (Zrozum różnicę między Add-Migration a Update-Database – Kiedy należy ich używać?)
Stwórz formularz w Windows Forms do dodawania użytkowników do bazy. Wyświetl listę użytkowników w DataGridView. Dodaj możliwość edytowania i usuwania użytkowników z bazy danych.
Utwórz modele Order i połącz je z User (relacja 1:N). Zaktualizuj bazę danych po dodaniu nowych modeli. Utwórz formularz do wyświetlania zamówień danego użytkownika.
Użyj biblioteki Bogus do generowania przykładowych danych użytkowników i zamówień. Dodaj metodę seedującą, która dodaje dane do bazy przy starcie aplikacji.