Wprowadzenie do formularzy systemu Windows
W tym przewodniku krok po kroku pokazano, jak utworzyć prostą aplikację Windows Forms (WinForms) wspieraną przez bazę danych SQLite. Aplikacja używa programu Entity Framework Core (EF Core) do ładowania danych z bazy danych, śledzenia zmian wprowadzonych w tych danych i utrwalania tych zmian z powrotem w bazie danych.
Zrzuty ekranu i listy kodu w tym przewodniku pochodzą z programu Visual Studio 2022 17.3.0.
Napiwek
Przykład z tego artykułu można zobaczyć w witrynie GitHub.
Wymagania wstępne
Aby ukończyć ten przewodnik, musisz mieć zainstalowany program Visual Studio 2022 w wersji 17.3 lub nowszej z obciążeniem pulpitu platformy .NET. Aby uzyskać więcej informacji na temat instalowania najnowszej wersji programu Visual Studio, zobacz Instalowanie programu Visual Studio.
Tworzenie aplikacji
Otwórz program Visual Studio.
W oknie uruchamiania wybierz pozycję Utwórz nowy projekt.
Wybierz pozycję Aplikacja Windows Forms, a następnie wybierz pozycję Dalej.
Na następnym ekranie nadaj projektowi nazwę, na przykład GetStartedWinForms, a następnie wybierz pozycję Dalej.
Na następnym ekranie wybierz wersję platformy .NET do użycia. Ten przewodnik został utworzony za pomocą platformy .NET 7, ale powinien również pracować z nowszymi wersjami.
Wybierz pozycję Utwórz.
Instalowanie pakietów NuGet platformy EF Core
Kliknij rozwiązanie prawym przyciskiem myszy i wybierz polecenie Zarządzaj pakietami NuGet dla rozwiązania...
Wybierz kartę Przeglądaj i wyszukaj ciąg "Microsoft.EntityFrameworkCore.Sqlite".
Wybierz pakiet Microsoft.EntityFrameworkCore.Sqlite.
Sprawdź projekt GetStartedWinForms w okienku po prawej stronie.
Wybierz najnowszą wersję. Aby użyć wersji wstępnej, upewnij się, że pole Uwzględnij wersję wstępną jest zaznaczone.
Kliknij pozycję Zainstaluj
Uwaga
Microsoft.EntityFrameworkCore.Sqlite to pakiet "dostawca bazy danych" do korzystania z programu EF Core z bazą danych SQLite. Podobne pakiety są dostępne dla innych systemów baz danych. Zainstalowanie pakietu dostawcy bazy danych automatycznie wprowadza wszystkie zależności potrzebne do korzystania z programu EF Core z tym systemem bazy danych. Obejmuje to pakiet podstawowy Microsoft.EntityFrameworkCore .
Definiowanie modelu
W tym przewodniku zaimplementujemy model przy użyciu polecenia "Code First". Oznacza to, że program EF Core utworzy tabele i schemat bazy danych na podstawie zdefiniowanych klas języka C#. Zobacz Zarządzanie schematami baz danych, aby zobaczyć, jak zamiast tego używać istniejącej bazy danych.
Kliknij prawym przyciskiem myszy projekt i wybierz polecenie Dodaj, a następnie pozycję Klasa... , aby dodać nową klasę.
Użyj nazwy pliku
Product.cs
i zastąp kod klasy:using System.ComponentModel; namespace GetStartedWinForms; public class Product { public int ProductId { get; set; } public string? Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } = null!; }
Powtórz polecenie , aby utworzyć
Category.cs
przy użyciu następującego kodu:using Microsoft.EntityFrameworkCore.ChangeTracking; namespace GetStartedWinForms; public class Category { public int CategoryId { get; set; } public string? Name { get; set; } public virtual ObservableCollectionListSource<Product> Products { get; } = new(); }
Właściwość Products
klasy Category
i Category
właściwości w Product
klasie są nazywane "nawigacjami". W programie EF Core nawigacje definiują relację między dwoma typami jednostek. W takim przypadku nawigacja Product.Category
odwołuje się do kategorii, do której należy dany produkt. Podobnie nawigacja Category.Products
po kolekcji zawiera wszystkie produkty dla danej kategorii.
Napiwek
W przypadku korzystania z formularzy systemu Windows element , ObservableCollectionListSource
który implementuje IListSource
element , może służyć do nawigacji kolekcji. Nie jest to konieczne, ale poprawia środowisko dwukierunkowego powiązania danych.
Definiowanie elementu DbContext
W programie EF Core klasa pochodna DbContext
jest używana do konfigurowania typów jednostek w modelu i działania jako sesji interakcji z bazą danych. W najprostszym przypadku DbContext
klasa:
- Zawiera
DbSet
właściwości dla każdego typu jednostki w modelu. - Zastępuje metodę w celu skonfigurowania
OnConfiguring
dostawcy bazy danych i parametry połączenia do użycia. Aby uzyskać więcej informacji, zobacz Konfigurowanie elementu DbContext .
W tym przypadku klasa DbContext zastępuje również metodę OnModelCreating
w celu dostarczenia przykładowych danych dla aplikacji.
Dodaj nową ProductsContext.cs
klasę do projektu przy użyciu następującego kodu:
using Microsoft.EntityFrameworkCore;
namespace GetStartedWinForms;
public class ProductsContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite("Data Source=products.db");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasData(
new Category { CategoryId = 1, Name = "Cheese" },
new Category { CategoryId = 2, Name = "Meat" },
new Category { CategoryId = 3, Name = "Fish" },
new Category { CategoryId = 4, Name = "Bread" });
modelBuilder.Entity<Product>().HasData(
new Product { ProductId = 1, CategoryId = 1, Name = "Cheddar" },
new Product { ProductId = 2, CategoryId = 1, Name = "Brie" },
new Product { ProductId = 3, CategoryId = 1, Name = "Stilton" },
new Product { ProductId = 4, CategoryId = 1, Name = "Cheshire" },
new Product { ProductId = 5, CategoryId = 1, Name = "Swiss" },
new Product { ProductId = 6, CategoryId = 1, Name = "Gruyere" },
new Product { ProductId = 7, CategoryId = 1, Name = "Colby" },
new Product { ProductId = 8, CategoryId = 1, Name = "Mozzela" },
new Product { ProductId = 9, CategoryId = 1, Name = "Ricotta" },
new Product { ProductId = 10, CategoryId = 1, Name = "Parmesan" },
new Product { ProductId = 11, CategoryId = 2, Name = "Ham" },
new Product { ProductId = 12, CategoryId = 2, Name = "Beef" },
new Product { ProductId = 13, CategoryId = 2, Name = "Chicken" },
new Product { ProductId = 14, CategoryId = 2, Name = "Turkey" },
new Product { ProductId = 15, CategoryId = 2, Name = "Prosciutto" },
new Product { ProductId = 16, CategoryId = 2, Name = "Bacon" },
new Product { ProductId = 17, CategoryId = 2, Name = "Mutton" },
new Product { ProductId = 18, CategoryId = 2, Name = "Pastrami" },
new Product { ProductId = 19, CategoryId = 2, Name = "Hazlet" },
new Product { ProductId = 20, CategoryId = 2, Name = "Salami" },
new Product { ProductId = 21, CategoryId = 3, Name = "Salmon" },
new Product { ProductId = 22, CategoryId = 3, Name = "Tuna" },
new Product { ProductId = 23, CategoryId = 3, Name = "Mackerel" },
new Product { ProductId = 24, CategoryId = 4, Name = "Rye" },
new Product { ProductId = 25, CategoryId = 4, Name = "Wheat" },
new Product { ProductId = 26, CategoryId = 4, Name = "Brioche" },
new Product { ProductId = 27, CategoryId = 4, Name = "Naan" },
new Product { ProductId = 28, CategoryId = 4, Name = "Focaccia" },
new Product { ProductId = 29, CategoryId = 4, Name = "Malted" },
new Product { ProductId = 30, CategoryId = 4, Name = "Sourdough" },
new Product { ProductId = 31, CategoryId = 4, Name = "Corn" },
new Product { ProductId = 32, CategoryId = 4, Name = "White" },
new Product { ProductId = 33, CategoryId = 4, Name = "Soda" });
}
}
Upewnij się, że w tym momencie skompiluj rozwiązanie .
Dodawanie kontrolek do formularza
Aplikacja wyświetli listę kategorii i listę produktów. Po wybraniu kategorii na pierwszej liście druga lista zmieni się, aby wyświetlić produkty dla tej kategorii. Te listy można modyfikować w celu dodawania, usuwania lub edytowania produktów i kategorii, a te zmiany można zapisać w bazie danych SQLite, klikając przycisk Zapisz .
Zmień nazwę formularza głównego z
Form1
naMainForm
.Zmień tytuł na "Produkty i kategorie".
Za pomocą przybornika dodaj dwie
DataGridView
kontrolki ułożone obok siebie.W właściwości dla pierwszego
DataGridView
elementu zmień nazwę nadataGridViewCategories
.W obszarze Właściwości dla drugiego
DataGridView
elementu zmień nazwę nadataGridViewProducts
.Ponadto za pomocą przybornika dodaj kontrolkę
Button
.Nadaj przyciskowi
buttonSave
nazwę i nadaj mu tekst "Zapisz". Formularz powinien wyglądać następująco:
Powiązanie danych
Następnym krokiem jest połączenie typów Product
i Category
z modelu z kontrolkami DataGridView
. Spowoduje to powiązanie danych załadowanych przez program EF Core z kontrolkami, tak aby jednostki śledzone przez program EF Core były synchronizowane z tymi wyświetlanymi w kontrolkach.
Kliknij przycisk Glyph akcji Projektanta na pierwszym
DataGridView
obiekcie . Jest to mały przycisk w prawym górnym rogu kontrolki.Spowoduje to otwarcie listy akcji, z której można uzyskać dostęp do listy rozwijanej Wybierz źródło danych. Nie utworzyliśmy jeszcze źródła danych, więc przejdź do dołu i wybierz pozycję Dodaj nowe źródło danych obiektu....
Wybierz pozycję Kategoria , aby utworzyć źródło danych obiektu dla kategorii, a następnie kliknij przycisk OK.
Napiwek
Jeśli w tym miejscu nie są wyświetlane żadne typy źródeł danych, upewnij się, że
Product.cs
element iProductsContext.cs
Category.cs
zostały dodane do projektu, a rozwiązanie zostało skompilowane.Teraz lista rozwijana Wybierz źródło danych zawiera właśnie utworzone źródło danych obiektu. Rozwiń węzeł Inne źródła danych, a następnie pozycję Project Data Sources (Źródła danych projektu) i wybierz pozycję Category (Kategoria).
Drugi
DataGridView
będzie powiązany z produktami. Jednak zamiast wiązać się z typem najwyższego poziomuProduct
, zamiast tego zostanie on powiązany z nawigacjąProducts
zCategory
powiązania pierwszegoDataGridView
elementu . Oznacza to, że po wybraniu kategorii w pierwszym widoku produkty dla tej kategorii będą automatycznie używane w drugim widoku.Korzystając z funkcji Glyph akcji projektanta w drugim
DataGridView
, wybierz pozycję Wybierz źródło danych, a następnie rozwińcategoryBindingSource
i wybierz pozycjęProducts
.
Konfigurowanie wyświetlanych funkcji
Domyślnie kolumna jest tworzona w DataGridView
obiekcie dla każdej właściwości powiązanych typów. Ponadto wartości dla każdej z tych właściwości można edytować przez użytkownika. Jednak niektóre wartości, takie jak wartości klucza podstawowego, są koncepcyjnie tylko do odczytu i nie powinny być edytowane. Ponadto niektóre właściwości, takie jak właściwość klucza obcego CategoryId
i nawigacja Category
, nie są przydatne dla użytkownika, a więc powinny być ukryte.
Napiwek
Często ukrywa się właściwości klucza podstawowego w rzeczywistej aplikacji. Są one widoczne tutaj, aby ułatwić sprawdzenie, co program EF Core robi za kulisami.
Kliknij prawym przyciskiem myszy pierwszy
DataGridView
i wybierz polecenie Edytuj kolumny....Ustaw kolumnę
CategoryId
, która reprezentuje klucz podstawowy, tylko do odczytu, a następnie kliknij przycisk OK.Kliknij prawym przyciskiem myszy drugą pozycję
DataGridView
i wybierz polecenie Edytuj kolumny.... Ustaw kolumnęProductId
jako tylko do odczytu, a następnie usuńCategoryId
kolumny iCategory
, a następnie kliknij przycisk OK.
Nawiązywanie połączenia z platformą EF Core
Aplikacja potrzebuje teraz niewielkiej ilości kodu, aby połączyć program EF Core z kontrolkami powiązanymi z danymi.
MainForm
Otwórz kod, klikając go prawym przyciskiem myszy i wybierając polecenie Wyświetl kod.Dodaj pole prywatne do przechowywania
DbContext
dla sesji i dodaj przesłonięcia dlaOnLoad
metod iOnClosing
. Kod powinien wyglądać następująco:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
}
}
Metoda jest wywoływana OnLoad
po załadowaniu formularza. W tej chwili
- Zostanie utworzone wystąpienie
ProductsContext
klasy , które będzie używane do ładowania i śledzenia zmian w produktach i kategoriach wyświetlanych przez aplikację. EnsureCreated
Metoda jest wywoływana w elemecieDbContext
, aby utworzyć bazę danych SQLite, jeśli jeszcze nie istnieje. Jest to szybki sposób tworzenia bazy danych podczas tworzenia prototypów lub testowania aplikacji. Jeśli jednak model ulegnie zmianie, należy usunąć bazę danych, aby można było ją utworzyć ponownie. (WierszEnsureDeleted
może być nieoznakowany, aby łatwo usunąć i ponownie utworzyć bazę danych po uruchomieniu aplikacji). Zamiast tego możesz użyć migracji platformy EF Core do modyfikowania i aktualizowania schematu bazy danych bez utraty danych.EnsureCreated
spowoduje również wypełnienie nowej bazy danych danymi zdefiniowanymi w metodzieProductsContext.OnModelCreating
.- Metoda
Load
rozszerzenia służy do ładowania wszystkich kategorii z bazy danych do klasyDbContext
. Te jednostki będą teraz śledzone przez elementDbContext
, który wykryje wszelkie zmiany wprowadzone podczas edycji kategorii przez użytkownika. - Właściwość
categoryBindingSource.DataSource
jest inicjowana do kategorii, które są śledzone przez elementDbContext
. Odbywa się to przez wywołanieLocal.ToBindingList()
Categories
DbSet
właściwości .Local
zapewnia dostęp do lokalnego widoku śledzonych kategorii z zdarzeniami podłączonymi w celu zapewnienia, że dane lokalne pozostają zsynchronizowane z wyświetlanymi danymi i na odwrót.ToBindingList()
Uwidacznia te dane jakoIBindingList
element , który jest rozumiany przez powiązanie danych formularzy systemu Windows.
Metoda jest wywoływana OnClosing
po zamknięciu formularza. W tej chwili obiekt DbContext
zostanie usunięty, co gwarantuje, że wszystkie zasoby bazy danych zostaną zwolnione, a dbContext
pole zostanie ustawione na wartość null, aby nie można było go ponownie użyć.
Wypełnianie widoku Produkty
Jeśli aplikacja została uruchomiona w tym momencie, powinna wyglądać mniej więcej tak:
Zwróć uwagę, że kategorie zostały załadowane z bazy danych, ale tabela products pozostaje pusta. Ponadto przycisk Zapisz nie działa.
Aby wypełnić tabelę products, program EF Core musi załadować produkty z bazy danych dla wybranej kategorii. Aby to osiągnąć:
W projektancie formularza głównego wybierz dla
DataGridView
kategorii .W obszarze Właściwości
DataGridView
programu wybierz zdarzenia (przycisk błyskawica), a następnie kliknij dwukrotnie zdarzenie SelectionChanged .Spowoduje to utworzenie wycinku w kodzie formularza głównego dla zdarzenia, które ma zostać wyzwolone za każdym razem, gdy wybór kategorii ulegnie zmianie.
Wypełnij kod zdarzenia:
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
W tym kodzie, jeśli istnieje aktywna (niepusta) DbContext
sesja, uzyskamy Category
wystąpienie powiązane z aktualnie wybranym wierszem .DataViewGrid
(Może to być null
, jeśli wybrano ostatni wiersz w widoku, który jest używany do tworzenia nowych kategorii). Jeśli istnieje wybrana kategoria, DbContext
zostanie ona poinstruowana o załadowaniu produktów skojarzonych z tą kategorią. Odbywa się to przez:
- Pobieranie elementu
EntityEntry
dlaCategory
wystąpienia (dbContext.Entry(category)
) - Poinformuj platformę
Products
EF Core o tym, że chcemy pracować na nawigacjiCategory
po kolekcji (.Collection(e => e.Products)
) - Na koniec informujemy platformę EF Core, że chcemy załadować kolekcję produktów z bazy danych (
.Load();
)
Napiwek
Po Load
wywołaniu program EF Core będzie uzyskiwać dostęp do bazy danych tylko w celu załadowania produktów, jeśli nie zostały jeszcze załadowane.
Jeśli aplikacja zostanie uruchomiona ponownie, należy załadować odpowiednie produkty za każdym razem, gdy wybrano kategorię:
Zapisywanie zmian
Na koniec przycisk Zapisz można połączyć z platformą EF Core, aby wszelkie zmiany wprowadzone w produktach i kategoriach zostały zapisane w bazie danych.
W projektancie formularza głównego wybierz przycisk Zapisz .
W obszarze Właściwości
Button
wybierz zdarzenia (przycisk pioruna ), a następnie kliknij dwukrotnie zdarzenie Kliknij .Wypełnij kod zdarzenia:
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
Ten kod wywołuje SaveChanges
metodę DbContext
, która zapisuje wszelkie zmiany wprowadzone w bazie danych SQLite. Jeśli nie wprowadzono żadnych zmian, jest to operacja bez operacji i nie zostanie wykonane żadne wywołanie bazy danych. Po zapisaniu kontrolki DataGridView
zostaną odświeżone. Dzieje się tak, ponieważ program EF Core odczytuje wygenerowane wartości klucza podstawowego dla wszystkich nowych produktów i kategorii z bazy danych. Wywoływanie Refresh
aktualizacji ekranu przy użyciu tych wygenerowanych wartości.
Ostateczna aplikacja
Oto pełny kod formularza głównego:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace GetStartedWinForms
{
public partial class MainForm : Form
{
private ProductsContext? dbContext;
public MainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.dbContext = new ProductsContext();
// Uncomment the line below to start fresh with a new database.
// this.dbContext.Database.EnsureDeleted();
this.dbContext.Database.EnsureCreated();
this.dbContext.Categories.Load();
this.categoryBindingSource.DataSource = dbContext.Categories.Local.ToBindingList();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this.dbContext?.Dispose();
this.dbContext = null;
}
private void dataGridViewCategories_SelectionChanged(object sender, EventArgs e)
{
if (this.dbContext != null)
{
var category = (Category)this.dataGridViewCategories.CurrentRow.DataBoundItem;
if (category != null)
{
this.dbContext.Entry(category).Collection(e => e.Products).Load();
}
}
}
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
}
}
Teraz można uruchomić aplikację, a produkty i kategorie można dodawać, usuwać i edytować. Zwróć uwagę, że jeśli przycisk Zapisz zostanie kliknięty przed zamknięciem aplikacji, wszystkie wprowadzone zmiany zostaną zapisane w bazie danych i ponownie załadowane po ponownym uruchomieniu aplikacji. Jeśli nie klikniesz przycisku Zapisz , wszelkie zmiany zostaną utracone po ponownym uruchomieniu aplikacji.
Napiwek
Do pustego wiersza w dolnej części kontrolki można dodać nową kategorię DataViewControl
lub produkt. Wiersz można usunąć, wybierając go i naciskając Del .
Przed zapisaniem
Po zapisaniu
Zwróć uwagę, że wartości klucza podstawowego dla dodanej kategorii i produktów są wypełniane po kliknięciu przycisku Zapisz .