Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In dieser Schritt-für-Schritt-Anleitung wird gezeigt, wie POCO-Typen an Windows Forms-Steuerelemente in einem "Master-Detail"-Formular gebunden werden. Die Anwendung verwendet Entity Framework, um Objekte mit Daten aus der Datenbank aufzufüllen, Änderungen nachzuverfolgen und Daten in der Datenbank beizubehalten.
Das Modell definiert zwei Typen, die an einer 1:n-Beziehung teilnehmen: Kategorie (Prinzipal\Master) und Produkt (abhängig\Detail). Anschließend werden die Visual Studio-Tools verwendet, um die im Modell definierten Typen an die WinForms-Steuerelemente zu binden. Das WinForms-Datenbindungsframework ermöglicht die Navigation zwischen verwandten Objekten: Das Auswählen von Zeilen in der Masteransicht bewirkt, dass die Detailansicht mit den entsprechenden untergeordneten Daten aktualisiert wird.
Die Screenshots und Codeauflistungen in dieser exemplarischen Vorgehensweise stammen aus Visual Studio 2013, aber Sie können diese exemplarische Vorgehensweise mit Visual Studio 2012 oder Visual Studio 2010 ausführen.
Voraussetzungen
Sie müssen Visual Studio 2013, Visual Studio 2012 oder Visual Studio 2010 installiert haben, um diese exemplarische Vorgehensweise abzuschließen.
Wenn Sie Visual Studio 2010 verwenden, müssen Sie auch NuGet installieren. Weitere Informationen finden Sie unter Installieren von NuGet.
Erstellen der Anwendung
- Öffnen Sie Visual Studio.
- Datei -> Neu -> Projekt….
- Auswählen von Windows im linken Bereich und Windows FormsApplication im rechten Bereich
- Geben Sie WinFormswithEFSample als Namen ein.
- Wählen Sie OK aus.
Installieren des Entity Framework NuGet-Pakets
- Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das WinFormswithEFSample-Projekt
- Wählen Sie „NuGet-Pakete verwalten…“
- Wählen Sie im Dialogfeld "NuGet-Pakete verwalten" die Registerkarte "Online " und dann das EntityFramework-Paket aus.
- Klicken Sie auf Install (Installieren).
Hinweis
Zusätzlich zur EntityFramework-Assembly wird auch ein Verweis auf System.ComponentModel.DataAnnotations hinzugefügt. Wenn das Projekt über einen Verweis auf System.Data.Entity verfügt, wird es beim Installieren des EntityFramework-Pakets entfernt. Die System.Data.Entity-Assembly wird nicht mehr für Entity Framework 6-Anwendungen verwendet.
Implementieren von IListSource für Sammlungen
Sammlungseigenschaften müssen die IListSource-Schnittstelle implementieren, um die bidirektionale Datenbindung beim Sortieren bei Verwendung von Windows Forms zu ermöglichen. Dazu erweitern wir ObservableCollection, um IListSource-Funktionen hinzuzufügen.
- Fügen Sie dem Projekt eine ObservableListSource-Klasse hinzu:
- Klicken Sie mit der rechten Maustaste auf den Projektnamen.
- Wählen Sie "Hinzufügen> " – Neues Element" aus.
- Wählen Sie "Klasse " aus, und geben Sie "ObservableListSource " für den Klassennamen ein.
- Ersetzen Sie den standardmäßig generierten Code durch den folgenden Code:
Diese Klasse ermöglicht die bidirektionale Datenbindung sowie das Sortieren. Die Klasse wird von ObservableCollection<T> abgeleitet und fügt eine explizite Implementierung von IListSource hinzu. Die GetList()-Methode von IListSource wird implementiert, um eine IBindingList-Implementierung zurückzugeben, die mit observableCollection synchronisiert bleibt. Die von ToBindingList generierte IBindingList-Implementierung unterstützt die Sortierung. Die ToBindingList-Erweiterungsmethode wird in der EntityFramework-Assembly definiert.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Data.Entity;
namespace WinFormswithEFSample
{
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
private IBindingList _bindingList;
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList()
{
return _bindingList ?? (_bindingList = this.ToBindingList());
}
}
}
Definieren eines Modells
In dieser exemplarischen Vorgehensweise können Sie ein Modell mithilfe von Code First oder EF Designer implementieren. Führen Sie einen der beiden folgenden Abschnitte aus.
Option 1: Definieren eines Modells mithilfe von Code Zuerst
In diesem Abschnitt wird gezeigt, wie Sie ein Modell und die zugehörige Datenbank mithilfe von Code First erstellen. Springen Sie zum nächsten Abschnitt (Option 2: Definieren eines Modells mithilfe von Database First), wenn Sie lieber "Database First" verwenden möchten, um Ihr Modell mithilfe des EF-Designers aus einer Datenbank rückzuentwickeln.
Beim Verwenden der Code First-Entwicklung beginnen Sie in der Regel mit dem Schreiben von .NET Framework-Klassen, die Ihr konzeptionelles (Domänen)-Modell definieren.
- Hinzufügen einer neuen Produktklasse zum Projekt
- Ersetzen Sie den standardmäßig generierten Code durch den folgenden Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
}
- Fügen Sie dem Projekt eine Category-Klasse hinzu.
- Ersetzen Sie den standardmäßig generierten Code durch den folgenden Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
public class Category
{
private readonly ObservableListSource<Product> _products =
new ObservableListSource<Product>();
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ObservableListSource<Product> Products { get { return _products; } }
}
}
Zusätzlich zum Definieren von Entitäten müssen Sie eine Klasse definieren, die von DbContext abgeleitet wird und DbSet<TEntity-Eigenschaften> verfügbar macht. Die DbSet-Eigenschaften lassen den Kontext wissen, welche Typen in das Modell eingeschlossen werden sollen. Die Typen DbContext und DbSet werden in der EntityFramework-Assembly definiert.
Eine Instanz des vom DbContext abgeleiteten Typs verwaltet die Entitätsobjekte während der Laufzeit, einschließlich auffüllen von Objekten mit Daten aus einer Datenbank, Änderungsnachverfolgung und Beibehalten von Daten in der Datenbank.
- Fügen Sie dem Projekt eine neue ProductContext-Klasse hinzu.
- Ersetzen Sie den standardmäßig generierten Code durch den folgenden Code:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
namespace WinFormswithEFSample
{
public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
}
Kompilieren Sie das Projekt.
Option 2: Definieren eines Modells mithilfe der Datenbank zuerst
In diesem Abschnitt wird gezeigt, wie Sie Ihr Modell mithilfe des EF-Designers aus einer Datenbank ableiten können. Wenn Sie den vorherigen Abschnitt abgeschlossen haben (Option 1: Definieren eines Modells mithilfe von Code First), überspringen Sie diesen Abschnitt, und wechseln Sie direkt zum Abschnitt "Lazy Loading ".
Erstellen einer vorhandenen Datenbank
In der Regel, wenn Sie auf eine vorhandene Datenbank abzielen, wird sie bereits erstellt, aber für diese exemplarische Vorgehensweise müssen wir eine Datenbank erstellen, auf die zugegriffen werden kann.
Der Datenbankserver, der mit Visual Studio installiert ist, unterscheidet sich je nach installierter Version von Visual Studio:
- Wenn Sie Visual Studio 2010 verwenden, erstellen Sie eine SQL Express-Datenbank.
- Wenn Sie Visual Studio 2012 verwenden, erstellen Sie eine LocalDB-Datenbank .
Lassen Sie uns fortfahren und die Datenbank generieren.
Ansicht –> Server-Explorer
Klicken Sie mit der rechten Maustaste auf Datenverbindungen –> Verbindung hinzufügen...
Wenn Sie im Server-Explorer noch keine Verbindung mit einer Datenbank hergestellt haben, müssen Sie Microsoft SQL Server als Datenquelle auswählen.
Stellen Sie eine Verbindung mit LocalDB oder SQL Express her, je nachdem, welches Sie installiert haben, und geben Sie "Produkte " als Datenbanknamen ein.
Wählen Sie "OK" aus, und Sie werden gefragt, ob Sie eine neue Datenbank erstellen möchten, wählen Sie "Ja" aus.
Die neue Datenbank wird nun im Server-Explorer angezeigt, klicken Sie mit der rechten Maustaste darauf, und wählen Sie "Neue Abfrage" aus.
Kopieren Sie die folgende SQL-Datei in die neue Abfrage, klicken Sie dann mit der rechten Maustaste auf die Abfrage, und wählen Sie "Ausführen" aus.
CREATE TABLE [dbo].[Categories] (
[CategoryId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId])
)
CREATE TABLE [dbo].[Products] (
[ProductId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
[CategoryId] [int] NOT NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId])
)
CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId])
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE
Reverse Engineering-Modell
Wir verwenden den Entity Framework-Designer, der als Teil von Visual Studio enthalten ist, um unser Modell zu erstellen.
Projekt –> Neues Element hinzufügen...
Wählen Sie Daten aus dem linken Menü und dann ADO.NET Entity Data Model
Geben Sie "ProductModel " als Namen ein, und klicken Sie auf "OK".
Dadurch wird der Entitätsdatenmodell-Assistent gestartet.
Wählen Sie "Aus Datenbank generieren " aus, und klicken Sie auf "Weiter".
Wählen Sie die Verbindung mit der Datenbank aus, die Sie im ersten Abschnitt erstellt haben, geben Sie "ProductContext" als Namen der Verbindungszeichenfolge ein, und klicken Sie auf "Weiter".
Klicken Sie auf das Kontrollkästchen neben 'Tabellen', um alle Tabellen zu importieren, und klicken Sie auf 'Fertig stellen'.
Sobald der Reverse Engineering-Prozess abgeschlossen ist, wird das neue Modell zu Ihrem Projekt hinzugefügt und geöffnet, damit Sie im Entity Framework Designer anzeigen können. Außerdem wurde Ihrem Projekt eine App.config Datei mit den Verbindungsdetails für die Datenbank hinzugefügt.
Zusätzliche Schritte in Visual Studio 2010
Wenn Sie in Visual Studio 2010 arbeiten, müssen Sie den EF-Designer aktualisieren, um ef6-Codegenerierung zu verwenden.
- Klicken Sie im EF-Designer mit der rechten Maustaste auf eine leere Stelle Ihres Modells, und wählen Sie " Codegenerierungselement hinzufügen" aus...
- Wählen Sie im linken Menü "Onlinevorlagen" aus, und suchen Sie nach "DbContext".
- Wählen Sie den EF 6.x DbContext-Generator für C# aus, geben Sie "ProductsModel " als Namen ein, und klicken Sie auf "Hinzufügen".
Aktualisieren der Codegenerierung für die Datenbindung
EF generiert Code aus Ihrem Modell mithilfe von T4-Vorlagen. Die vorlagen, die mit Visual Studio ausgeliefert oder aus dem Visual Studio-Katalog heruntergeladen wurden, sind für die allgemeine Verwendung vorgesehen. Dies bedeutet, dass die aus diesen Vorlagen generierten Entitäten einfache ICollection<T-Eigenschaften> aufweisen. Bei der Datenbindung ist es jedoch wünschenswert, Sammlungseigenschaften zu haben, die IListSource implementieren. Deshalb haben wir oben die ObservableListSource-Klasse erstellt, und wir werden jetzt die Vorlagen ändern, um diese Klasse zu verwenden.
Öffnen Sie den Projektmappen-Explorer , und suchen Sie die Datei "ProductModel.edmx "
Suchen Sie die ProductModel.tt Datei, die unter der Datei "ProductModel.edmx" geschachtelt wird.
Doppelklicken Sie auf die ProductModel.tt Datei, um sie im Visual Studio-Editor zu öffnen.
Suchen und ersetzen Sie die beiden Vorkommen von "ICollection" durch "ObservableListSource". Diese befinden sich ungefähr bei den Zeilen 296 und 484.
Suchen und ersetzen Sie das erste Vorkommen von "HashSet" durch "ObservableListSource". Dieses Vorkommen befindet sich bei etwa Zeile 50. Ersetzen Sie nicht das zweite Vorkommen von HashSet, das später im Code gefunden wurde.
Speichern Sie die ProductModel.tt Datei. Dies sollte dazu führen, dass der Code für Entitäten neu generiert wird. Wenn der Code nicht automatisch neu generiert wird, klicken Sie mit der rechten Maustaste auf ProductModel.tt, und wählen Sie "Benutzerdefiniertes Tool ausführen" aus.
Wenn Sie nun die Category.cs-Datei (die unter ProductModel.tt geschachtelt ist) öffnen, sollten Sie sehen, dass die Products-Auflistung den Typ ObservableListSource-Produkt<> aufweist.
Kompilieren Sie das Projekt.
Faules Laden
Die Products-Eigenschaft der Category-Klasse und die Category-Eigenschaft der Product-Klasse sind Navigationseigenschaften. In Entity Framework bieten Navigationseigenschaften eine Möglichkeit zum Navigieren in einer Beziehung zwischen zwei Entitätstypen.
EF bietet Ihnen die Möglichkeit, verwandte Entitäten automatisch aus der Datenbank zu laden, wenn Sie zum ersten Mal auf die Navigationseigenschaft zugreifen. Beachten Sie bei diesem Ladetyp (als faules Laden bezeichnet), dass beim ersten Zugriff auf jede Navigationseigenschaft eine separate Abfrage für die Datenbank ausgeführt wird, wenn sich der Inhalt nicht bereits im Kontext befindet.
Bei der Verwendung von POCO-Entitätstypen erreicht EF Lazy Loading, indem es während der Laufzeit Instanzen von abgeleiteten Proxytypen erstellt und dann die virtuellen Eigenschaften Ihrer Klassen überschreibt, um eine Ladeunterbrechung hinzuzufügen. Um Lazy Loading von verwandten Objekten zu erreichen, müssen Sie Navigationseigenschafts-Getter als öffentlich und virtuell (in Visual Basic überschreibbar) deklarieren, und die Klasse darf nicht versiegelt sein (NotOverridable in Visual Basic). Bei Verwendung des Datenbank-First-Ansatzes werden Navigationseigenschaften automatisch virtuell gemacht, um Lazy Loading zu ermöglichen. Im Abschnitt "Code First" haben wir uns entschieden, die Navigationseigenschaften aus demselben Grund virtuell zu machen
Objekt an Steuerelemente binden
Fügen Sie die Klassen hinzu, die im Modell als Datenquellen für diese WinForms-Anwendung definiert sind.
Wählen Sie im Hauptmenü "Projekt> - Neue Datenquelle hinzufügen" aus ... (in Visual Studio 2010 müssen Sie "Daten>" auswählen – Neue Datenquelle hinzufügen...)
Wählen Sie im Fenster "Datenquellentyp auswählen" " Objekt " aus, und klicken Sie auf "Weiter".
Im Dialogfeld "Datenobjekte auswählen" falten Sie WinFormswithEFSample zweimal auf und wählen Kategorie aus. Es ist nicht erforderlich, die Datenquelle "Produkt" auszuwählen, da wir über die Eigenschaft "Produkt" in der Datenquelle "Kategorie" darauf zugreifen.
Klicken Sie auf "Fertig stellen". Wenn das Fenster "Datenquellen" nicht angezeigt wird, wählen Sie "Ansicht –> Andere Windows-Datenquellen>" aus.
Drücken Sie das Pinsymbol, sodass das Fenster "Datenquellen" nicht automatisch ausgeblendet wird. Möglicherweise müssen Sie auf die Aktualisierungsschaltfläche klicken, wenn das Fenster bereits sichtbar war.
Doppelklicken Sie im Projektmappen-Explorer auf die datei Form1.cs , um das Hauptformular im Designer zu öffnen.
Wählen Sie die Kategoriedatenquelle aus, und ziehen Sie sie auf das Formular. Standardmäßig werden dem Designer ein neues DataGridView (categoryDataGridView)-Steuerelement und eine Navigationssymbolleiste hinzugefügt. Diese Steuerelemente sind an die Komponenten BindingSource (categoryBindingSource) und Binding Navigator (categoryBindingNavigator) gebunden, die ebenfalls erstellt werden.
Bearbeiten Sie die Spalten in der categoryDataGridView. Wir möchten die Spalte "CategoryId" auf schreibgeschützt setzen. Der Wert für die CategoryId-Eigenschaft wird von der Datenbank generiert, nachdem die Daten gespeichert wurden.
- Klicken Sie mit der rechten Maustaste auf das DataGridView-Steuerelement, und wählen Sie "Spalten bearbeiten" aus...
- Wählen Sie die Spalte "CategoryId" aus, und legen Sie "ReadOnly" auf "True" fest.
- Drücken Sie "OK".
Wählen Sie "Produkte" aus der Datenquelle "Kategorie" aus, und ziehen Sie sie auf das Formular. Die productDataGridView und productBindingSource werden dem Formular hinzugefügt.
Bearbeiten Sie die Spalten in "productDataGridView". Wir möchten die Spalten "CategoryId" und "Category" ausblenden und "ProductId" auf schreibgeschützt setzen. Der Wert für die ProductId-Eigenschaft wird von der Datenbank generiert, nachdem die Daten gespeichert wurden.
- Klicken Sie mit der rechten Maustaste auf das DataGridView-Steuerelement, und wählen Sie "Spalten bearbeiten" aus.
- Wählen Sie die Spalte "ProductId" aus, und legen Sie "ReadOnly" auf "True" fest.
- Wählen Sie die Spalte "CategoryId " aus, und drücken Sie die Schaltfläche "Entfernen" . Führen Sie dieselbe Vorgehensweise mit der Spalte " Kategorie " aus.
- Klicken Sie auf OK.
Bisher haben wir unsere DataGridView-Steuerelemente mit BindingSource-Komponenten im Designer verknüpft. Im nächsten Abschnitt fügen wir Code zum Code-Behind hinzu, um `categoryBindingSource.DataSource` auf die Sammlung von Entitäten festzulegen, die derzeit von `DbContext` nachverfolgt werden. Wenn wir Produkte aus der Kategorie gezogen und abgelegt haben, kümmerte sich WinForms um die Einrichtung der Eigenschaft productsBindingSource.DataSource auf 'categoryBindingSource' und der Eigenschaft productsBindingSource.DataMember auf 'Products'. Aufgrund dieser Bindung werden nur die Produkte, die zur aktuell ausgewählten Kategorie gehören, in "productDataGridView" angezeigt.
Aktivieren Sie die Schaltfläche " Speichern " auf der Navigationssymbolleiste, indem Sie auf die rechte Maustaste klicken und "Aktiviert" auswählen.
Fügen Sie den Ereignishandler für die Schaltfläche "Speichern" hinzu, indem Sie auf die Schaltfläche doppelklicken. Dadurch wird der Ereignishandler hinzugefügt und Sie gelangen zum Code-Behind des Formulars. Der Code für den categoryBindingNavigatorSaveItem_Click-Ereignishandler wird im nächsten Abschnitt hinzugefügt.
Hinzufügen des Codes, der die Dateninteraktion behandelt
Wir fügen nun den Code hinzu, um den ProductContext zum Ausführen des Datenzugriffs zu verwenden. Aktualisieren Sie den Code für das Hauptformularfenster wie unten dargestellt.
Der Code erstellt eine lang laufende Instanz von ProductContext. Das ProductContext-Objekt wird verwendet, um Daten in der Datenbank abzufragen und zu speichern. Die Dispose()-Methode für die ProductContext-Instanz wird dann von der überschriebenen OnClosing-Methode aufgerufen. Die Codekommentare enthalten Details zur Funktionsweise des Codes.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.Entity;
namespace WinFormswithEFSample
{
public partial class Form1 : Form
{
ProductContext _context;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_context = new ProductContext();
// Call the Load method to get the data for the given DbSet
// from the database.
// The data is materialized as entities. The entities are managed by
// the DbContext instance.
_context.Categories.Load();
// Bind the categoryBindingSource.DataSource to
// all the Unchanged, Modified and Added Category objects that
// are currently tracked by the DbContext.
// Note that we need to call ToBindingList() on the
// ObservableCollection<TEntity> returned by
// the DbSet.Local property to get the BindingList<T>
// in order to facilitate two-way binding in WinForms.
this.categoryBindingSource.DataSource =
_context.Categories.Local.ToBindingList();
}
private void categoryBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
this.Validate();
// Currently, the Entity Framework doesn’t mark the entities
// that are removed from a navigation property (in our example the Products)
// as deleted in the context.
// The following code uses LINQ to Objects against the Local collection
// to find all products and marks any that do not have
// a Category reference as deleted.
// The ToList call is required because otherwise
// the collection will be modified
// by the Remove call while it is being enumerated.
// In most other situations you can do LINQ to Objects directly
// against the Local property without using ToList first.
foreach (var product in _context.Products.Local.ToList())
{
if (product.Category == null)
{
_context.Products.Remove(product);
}
}
// Save the changes to the database.
this._context.SaveChanges();
// Refresh the controls to show the values
// that were generated by the database.
this.categoryDataGridView.Refresh();
this.productsDataGridView.Refresh();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
}
Testen der Windows Forms-Anwendung
Kompilieren Sie die Anwendung, und führen Sie sie aus, und Sie können die Funktionalität testen.
Nach dem Speichern werden die generierten Speicherschlüssel auf dem Bildschirm angezeigt.
Wenn Sie Code First verwendet haben, sehen Sie auch, dass eine WinFormswithEFSample.ProductContext-Datenbank für Sie erstellt wird.