Erste Schritte mit Windows Forms
Diese Schrittanleitung zeigt Ihnen, wie Sie eine einfache Windows Forms-Anwendung (WinForms) erstellen, die durch eine SQLite-Datenbank gestützt wird. Die Anwendung verwendet Entity Framework Core (EF Core), um Daten aus der Datenbank zu laden, an diesen Daten vorgenommene Änderungen nachzuverfolgen und diese Änderungen wieder in der Datenbank zu speichern.
Die Screenshots und Codelistings in dieser exemplarischen Vorgehensweise stammen aus Visual Studio 2022 17.3.0.
Tipp
Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.
Voraussetzungen
Um diese exemplarische Vorgehensweise nachvollziehen zu können, müssen Sie Visual Studio 2022 17.3 oder höher mit ausgewählter .NET-Desktopworkload installiert haben. Weitere Informationen zur Installation der neuesten Version von Visual Studio finden Sie unter Installieren von Visual Studio.
Erstellen der Anwendung
Öffnen Sie Visual Studio.
Wählen Sie im Startfenster Neues Projekt erstellen aus.
Wählen Sie Windows Forms-App und dann Weiter aus.
Vergeben Sie auf dem nächsten Bildschirm einen Namen für das Projekt (z. B. GetStartedWinForms), und wählen Sie Weiter aus.
Wählen Sie auf dem nächsten Bildschirm die .NET-Version aus, die verwendet werden soll. Diese exemplarische Vorgehensweise wurde mit .NET 7 erstellt, sollte aber auch mit späteren Versionen funktionieren.
Wählen Sie Erstellen aus.
Installieren der EF Core-NuGet-Pakete
Klicken Sie mit der rechten Maustaste auf die Projektmappe, und wählen Sie NuGet-Pakete für Projektmappe verwalten aus.
Wählen Sie die Registerkarte Durchsuchen aus, und suchen Sie nach „Microsoft.EntityFrameworkCore.Sqlite“.
Wählen Sie das Paket Microsoft.EntityFrameworkCore.Sqlite aus.
Aktivieren Sie das Projekt GetStartedWinForms im rechten Bereich.
Wählen Sie die neueste Version aus. Um eine Vorabversion zu verwenden, stellen Sie sicher, dass das Kontrollkästchen Vorabversion einschließen aktiviert ist.
Klicken Sie auf Install (Installieren).
Hinweis
Microsoft.EntityFrameworkCore.Sqlite ist das Datenbankanbieterpaket für die Verwendung von EF Core mit einer SQLite-Datenbank. Ähnliche Pakete sind für andere Datenbanksysteme verfügbar. Durch die Installation eines Datenbankanbieterpakets werden automatisch alle Abhängigkeiten bereitgestellt, die für die Verwendung von EF Core mit dem zugehörigen Datenbanksystem erforderlich sind. Dies schließt das Microsoft.EntityFrameworkCore-Basispaket ein.
Definieren eines Modells
In dieser exemplarischen Vorgehensweise implementieren Sie ein Modell mit „Code First“. Dies bedeutet, dass EF Core die Datenbanktabellen und das Schema basierend auf den von Ihnen definierten C#-Klassen erstellt. Wenn Sie stattdessen eine vorhandene Datenbank verwenden möchten, finden Sie entsprechende Informationen unter Verwalten von Datenbankschemas.
Klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie Hinzufügen und dann Klasse... aus, um eine neue Klasse hinzuzufügen.
Verwenden Sie den Dateinamen
Product.cs
, und ersetzen Sie den Code für die Klasse durch Folgendes: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!; }
Wiederholen Sie diesen Vorgang mit dem folgenden Code, um
Category.cs
zu erstellen: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(); }
Die Products
-Eigenschaft in der Category
-Klasse und die Category
-Eigenschaft in der Product
--Klasse werden als „Navigationselemente“ bezeichnet. In EF Core definieren Navigationselemente eine Beziehung zwischen zwei Entitätstypen. In diesem Fall verweist das Navigationselement Product.Category
auf die Kategorie, zu der ein bestimmtes Produkt gehört. Das Navigationselement der Category.Products
-Sammlung enthält alle Produkte für eine bestimmte Kategorie.
Tipp
Beim Verwenden von Windows Forms kann das ObservableCollectionListSource
-Element, das IListSource
implementiert, für Sammlungsnavigationselemente verwendet werden. Dies ist nicht zwingend erforderlich, verbessert aber die bidirektionale Datenbindung.
Definieren von DbContext
In EF Core wird eine von DbContext
abgeleitete Klasse verwendet, um Entitätstypen in einem Modell zu konfigurieren und um als Sitzung für die Interaktion mit der Datenbank zu fungieren. Im einfachsten Fall handelt es sich um eine DbContext
-Klasse:
- Enthält
DbSet
-Eigenschaften für jeden Entitätstyp im Modell. - Überschreibt die
OnConfiguring
-Methode zum Konfigurieren des zu verwendenden Datenbankanbieters und der zu verwendenden Verbindungszeichenfolge. Weitere Informationen finden Sie unter Konfigurieren von DbContext.
In diesem Fall überschreibt die DbContext-Klasse auch die OnModelCreating
-Methode, um einige Beispieldaten für die Anwendung bereitzustellen.
Fügen Sie dem Projekt mit dem folgenden Code eine neue ProductsContext.cs
-Klasse hinzu:
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" });
}
}
Zu diesem Zeitpunkt müssen Sie die Lösung kompilieren.
Hinzufügen von Steuerelementen zum Formular
Die Anwendung zeigt eine Liste der Kategorien und eine Liste der Produkte an. Wenn in der ersten Liste eine Kategorie ausgewählt wird, ändert sich die zweite Liste und zeigt Produkte für diese Kategorie an. Diese Listen können geändert werden, um Produkte und Kategorien hinzuzufügen, zu entfernen oder zu bearbeiten. Sie können diese Änderungen in der SQLite-Datenbank speichern, indem Sie auf die Schaltfläche Speichern klicken.
Ändern Sie den Namen des Hauptformulars von
Form1
zuMainForm
.Ändern Sie auch den Titel zu „Produkte und Kategorien“.
Fügen Sie mithilfe der Toolbox zwei nebeneinander angeordnete
DataGridView
-Steuerelemente hinzu.Ändern Sie in den Eigenschaften für die erste
DataGridView
den Namen zudataGridViewCategories
.Ändern Sie in den Eigenschaften für die zweite
DataGridView
den Namen zudataGridViewProducts
.Fügen Sie, ebenfalls mithilfe der Toolbox, ein
Button
-Steuerelement hinzu.Benennen Sie die Schaltfläche
buttonSave
, und geben Sie als Text „Speichern“ ein. Das Formular sollte in etwa so aussehen:
Datenbindung
Der nächste Schritt besteht darin, die Typen Product
und Category
aus dem Modell mit den DataGridView
-Steuerelementen zu verbinden. Dadurch werden die von EF Core geladenen Daten an die Steuerelemente gebunden, sodass die von EF Core nachverfolgten Entitäten mit den in den Steuerelementen angezeigten Entitäten synchronisiert werden.
Klicken Sie im ersten
DataGridView
-Element auf die Designer-Aktionsglyphe. Dies ist die kleine Schaltfläche in der oberen rechten Ecke des Steuerelements.Dadurch wird die Aktionsliste geöffnet, von der aus auf die Dropdownliste für Datenquelle auswählen zugegriffen werden kann. Wir haben noch keine Datenquelle erstellt. Wählen Sie also unten in der Dropdownliste die Option Neue Objektdatenquelle hinzufügen... aus.
Wählen Sie Kategorie aus, um eine Objektdatenquelle für Kategorien zu erstellen, und klicken Sie auf OK.
Tipp
Wenn hier keine Datenquellentypen angezeigt werden, stellen Sie sicher, dass
Product.cs
,Category.cs
undProductsContext.cs
dem Projekt hinzugefügt wurden und dass die Lösung kompiliert wurde.Jetzt enthält die Dropdownliste Datenquelle auswählen die soeben erstellte Objektdatenquelle. Erweitern Sie Andere Datenquellen und dann Projektdatenquellen, und wählen Sie Kategorie aus.
Die zweite
DataGridView
wird an Produkte gebunden. Anstatt die Ansicht jedoch an denProduct
-Typ der obersten Ebene zu binden, wird sie von derCategory
-Bindung des erstenDataGridView
an dasProducts
-Navigationselement gebunden. Das bedeutet: Wenn in der ersten Ansicht eine Kategorie ausgewählt wird, werden in der zweiten Ansicht automatisch die Produkte für diese Kategorie automatisch.Wählen Sie mit der Designer-Aktionsglyphe in der zweiten
DataGridView
die Option Datenquelle auswählen aus, erweitern Sie dann diecategoryBindingSource
, und wählen SieProducts
aus.
Konfigurieren der angezeigten Elemente
Standardmäßig wird in der DataGridView
für jede Eigenschaft der gebundenen Typen eine Spalte erstellt. Außerdem können die Werte für jede dieser Eigenschaften vom Benutzer bearbeitet werden. Einige Werte wie z. B. die Primärschlüsselwerte sind jedoch konzeptbedingt schreibgeschützt und sollten daher nicht bearbeitet werden. Außerdem sind einige Eigenschaften wie die Fremdschlüsseleigenschaft CategoryId
und das Category
-Navigationselement für den Benutzer nicht nützlich und sollten daher ausgeblendet werden.
Tipp
Es ist üblich, Primärschlüsseleigenschaften in einer echten Anwendung auszublenden. In diesem Tutorial sind sie sichtbar, damit Sie besser erkennen können, welche Vorgänge EF Core hinter den Kulissen ausführt.
Klicken Sie mit der rechten Maustaste auf die
DataGridView
, und wählen Sie Spalten bearbeiten... aus.Legen Sie die Spalte
CategoryId
, die den Primärschlüssel repräsentiert, als schreibgeschützt fest, und klicken Sie auf OK.Klicken Sie mit der rechten Maustaste auf die zweite
DataGridView
, und wählen Sie Spalten bearbeiten... aus. Legen Sie die SpalteProductId
als schreibgeschützt fest, entfernen Sie die SpaltenCategoryId
undCategory
, und klicken Sie dann auf OK.
Verbinden mit EF Core
Die Anwendung benötigt jetzt etwas Code, um EF Core mit den datengebundenen Steuerelementen zu verbinden.
Öffnen Sie den Code für
MainForm
, indem Sie mit der rechten Maustaste auf die Datei klicken und Code anzeigen auswählen.Fügen Sie ein privates Feld hinzu, das den
DbContext
für die Sitzung enthalten soll, und fügen Sie Überschreibungen für die MethodenOnLoad
undOnClosing
hinzu. Der Code sollte wie folgt aussehen:
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;
}
}
}
Die OnLoad
-Methode wird aufgerufen, wenn das Formular geladen wird. Zu diesem Zeitpunkt geschieht Folgendes:
- Eine Instanz von
ProductsContext
wird erstellt, die zum Laden und Nachverfolgen von Änderungen an den von der Anwendung angezeigten Produkten und Kategorien verwendet wird. EnsureCreated
wird für denDbContext
aufgerufen, um die SQLite-Datenbank zu erstellen, sofern diese noch nicht vorhanden ist. Das ist eine schnelle Möglichkeit zum Erstellen einer Datenbank beim Erstellen von Prototyp- oder Testanwendungen. Wenn sich das Modell ändert, muss die Datenbank jedoch gelöscht werden, damit sie erneut erstellt werden kann. (Die Auskommentierung der ZeileEnsureDeleted
kann aufgehoben werden, damit die Datenbank einfach gelöscht und neu erstellt werden kann, wenn die Anwendung ausgeführt wird.) Sie können stattdessen EF Core-Migrationen verwenden, um das Datenbankschema zu ändern und zu aktualisieren, ohne dass Daten verloren gehen.EnsureCreated
füllt auch die neue Datenbank mit den in derProductsContext.OnModelCreating
-Methode definierten Daten auf.- Die Erweiterungsmethode
Load
wird verwendet, um alle Kategorien aus der Datenbank inDbContext
zu laden. Diese Entitäten werden nun vonDbContext
nachverfolgt. Damit werden alle Änderungen erkannt, die beim Bearbeiten von Kategorien durch Benutzer vorgenommen werden. - Die Eigenschaft
categoryBindingSource.DataSource
wird in den Kategorien initialisiert, die vonDbContext
nachverfolgt werden. Dazu wirdLocal.ToBindingList()
für dieCategories
-EigenschaftDbSet
aufgerufen.Local
ermöglicht den Zugriff auf eine lokale Ansicht der nachverfolgten Kategorien, wobei Ereignisse eingebunden sind, um sicherzustellen, dass die lokalen Daten mit den angezeigten Daten synchronisiert bleiben und umgekehrt.ToBindingList()
macht diese Daten alsIBindingList
verfügbar, die von der Windows Forms-Datenbindung verstanden wird.
Die OnClosing
-Methode wird aufgerufen, wenn das Formular geschlossen wird. Zu diesem Zeitpunkt wird DbContext
verworfen, wodurch sichergestellt wird, dass Datenbankressourcen freigegeben werden, und das Feld dbContext
wird auf NULL festgelegt, sodass es nicht erneut verwendet werden kann.
Auffüllen der Ansicht „Produkte“
Wenn die Anwendung an diesem Punkt gestartet wird, sollte sie etwa wie folgt aussehen:
Beachten Sie, dass die Kategorien aus der Datenbank geladen wurden, die Produkttabelle jedoch leer bleibt. Außerdem funktioniert die Schaltfläche Speichern nicht.
Zum Auffüllen der Produkttabelle muss EF Core Produkte für die ausgewählte Kategorie aus der Datenbank laden. Hierzu sind folgende Schritte erforderlich:
Wählen Sie im Designer für das Hauptformular die
DataGridView
für Kategorien aus.Wählen Sie in den Eigenschaften für
DataGridView
die Schaltfläche für Ereignisse (Blitz) aus, und doppelklicken Sie auf das Ereignis SelectionChanged.Dadurch wird im Hauptformularcode ein Stub erstellt, damit ein Ereignis ausgelöst wird, wenn sich die Kategorieauswahl ändert.
Geben Sie den Code für das Ereignis ein:
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();
}
}
}
Wenn in diesem Code eine aktive DbContext
Sitzung (nicht NULL) vorhanden ist, wird die Category
-Instanz abgerufen, die an die aktuell ausgewählte Zeile der DataViewGrid
gebunden ist. (Dies kann null
sein, wenn die letzte Zeile in der Ansicht ausgewählt ist, die zum Erstellen neuer Kategorien verwendet wird.) Wenn eine ausgewählte Kategorie vorhanden ist, wird DbContext
angewiesen, die mit dieser Kategorie verknüpften Produkte zu laden. Dies wird wie folgt erreicht:
- Es wird ein
EntityEntry
für dieCategory
-Instanz abgerufen (dbContext.Entry(category)
). - EF Core wird darüber informiert, dass das Navigationselement der Sammlung
Products
dieserCategory
verwendet werden soll (.Collection(e => e.Products)
). - Und schließlich wird EF Core mitgeteilt, dass diese Sammlung von Produkten aus der Datenbank geladen werden soll (
.Load();
).
Tipp
Wenn Load
aufgerufen wird, greift EF Core nur auf die Datenbank zu, um die Produkte zu laden, falls sie noch nicht geladen wurden.
Wenn die Anwendung jetzt erneut ausgeführt wird, sollte sie die entsprechenden Produkte laden, wenn eine Kategorie ausgewählt wird:
Änderungen werden gespeichert
Schließlich kann die Schaltfläche Speichern mit EF Core verbunden werden, sodass alle Änderungen, die an den Produkten und Kategorien vorgenommen wurden, in der Datenbank gespeichert werden.
Wählen Sie im Designer für das Hauptformular die Schaltfläche Speichern aus.
Wählen Sie in den Eigenschaften für
Button
die Schaltfläche für Ereignisse (Blitz) aus, und doppelklicken Sie auf das Ereignis Click.Geben Sie den Code für das Ereignis ein:
private void buttonSave_Click(object sender, EventArgs e)
{
this.dbContext!.SaveChanges();
this.dataGridViewCategories.Refresh();
this.dataGridViewProducts.Refresh();
}
Dieser Code ruft SaveChanges
für DbContext
auf, wodurch alle Änderungen gespeichert werden, die an der SQLite-Datenbank vorgenommen wurden. Wenn keine Änderungen vorgenommen wurden, werden keine Vorgänge verarbeitet, und es erfolgt kein Datenbankaufruf. Nach dem Speichern werden die DataGridView
-Steuerelemente aktualisiert. Dies liegt daran, dass EF Core generierte Primärschlüsselwerte für alle neuen Produkte und Kategorien aus der Datenbank liest. Durch Aufrufen von Refresh
wird die Anzeige mit diesen generierten Werten aktualisiert.
Die endgültige Anwendung
Hier sehen Sie den vollständigen Code für das Hauptformular:
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();
}
}
}
Die Anwendung kann jetzt ausgeführt werden, und Produkte und Kategorien können hinzugefügt, gelöscht und bearbeitet werden. Beachten Sie Folgendes: Wenn vor dem Schließen der Anwendung auf die Schaltfläche Speichern geklickt wird, werden alle vorgenommenen Änderungen in der Datenbank gespeichert und erneut geladen, wenn die Anwendung erneut gestartet wird. Wenn nicht auf Speichern geklickt wird, gehen alle Änderungen verloren, wenn die Anwendung erneut gestartet wird.
Tipp
Eine neue Kategorie oder ein neues Produkt kann einem DataViewControl
über die leere Zeile am unteren Rand des Steuerelements hinzugefügt werden. Eine Zeile kann gelöscht werden, indem Sie sie auswählen und die Taste ENTF drücken.
Vor dem Speichern
Nach dem Speichern
Beachten Sie, dass die Primärschlüsselwerte für die hinzugefügte Kategorie und Produkte aufgefüllt werden, wenn auf Speichern geklickt wird.