Datenbindung mit WPF
Wichtig
Dieses Dokument ist nur für WPF in .NET Framework
Dieses Dokument beschreibt die Datenbindung für WPF im .NET Framework. Für neue .NET Core-Projekte empfehlen wir, EF Core anstelle von Entity Framework 6 zu verwenden. Die Dokumentation für die Datenbindung in EF Core befindet sich hier: Erste Schritte mit WPF.
In dieser schrittweisen exemplarischen Vorgehensweise wird gezeigt, wie POCO-Typen an WPF-Steuerelemente in einem „master-detail“-Formular gebunden werden. Die Anwendung verwendet die Entity Framework-APIs zum Auffüllen von Objekten mit Daten aus der Datenbank, zum Nachverfolgen von Änderungen und zum persistenten Speichern von Daten in der Datenbank.
Das Modell definiert zwei Typen, die an einer 1:n-Beziehung beteiligt sind: Category (principal\master) und Product (dependent\detail). Anschließend werden die Visual Studio-Tools verwendet, um die im Modell definierten Typen an die WPF-Steuerelemente zu binden. Das WPF-Datenbindungsframework ermöglicht die Navigation zwischen verbundenen Objekten: durch Auswählen von Zeilen in der Masteransicht wird die Detailansicht mit den entsprechenden untergeordneten Daten aktualisiert.
Die Screenshots und Codeauflistungen in dieser exemplarischen Vorgehensweise stammen aus Visual Studio 2013, aber Sie können diese exemplarische Vorgehensweise auch mit Visual Studio 2012 oder Visual Studio 2010 ausführen.
Verwenden der Option „Object“ zum Erstellen von WPF-Datenquellen
Mit früherer Version von Entity Framework wurde empfohlen, die Option Datenbank beim Erstellen einer neuen Datenquelle basierend auf einem Modell zu verwenden, das mit dem EF-Designer erstellt wurde. Der Grund dafür war, dass der EF Designer einen Kontext generierte, der sich von ObjectContext ableitete, und Entitätsklassen, die sich von EntityObject ableiteten. Wenn Sie die Datenbankoption verwenden, können Sie den besten Code für die Interaktion mit dieser API-Oberfläche schreiben.
Die EF Designer für Visual Studio 2012 und Visual Studio 2013 generieren einen Kontext, der von DbContext zusammen mit einfachen POCO-Entitätsklassen abgeleitet wird. In Visual Studio 2010 wird empfohlen, eine Codegenerierungsvorlage zu verwenden, die DbContext verwendet, wie weiter unten in dieser exemplarischen Vorgehensweise beschrieben.
Wenn Sie die DbContext-API-Oberfläche verwenden, sollten Sie beim Erstellen einer neuen Datenquelle die Option Object verwenden, wie in dieser exemplarischen Vorgehensweise gezeigt.
Bei Bedarf können Sie für Modelle, die mit dem EF Designer erstellt wurden, zur ObjectContext-basierten Codegenerierung zurückkehren.
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 Installation von NuGet.
Erstellen der Anwendung
- Öffnen Sie Visual Studio.
- Datei -> Neu -> Projekt….
- Wählen Sie im linken Bereich Windows aus, und WPFApplication im rechten Bereich.
- Geben Sie WPFwithEFSample als Namen ein.
- Klicken Sie auf OK.
Installieren Sie das Entity Framework-NuGet-Paket.
- Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt WinFormswithEFSample.
- Wählen Sie NuGet-Pakete verwalten... aus.
- 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 für Entity Framework 6-Anwendungen nicht mehr verwendet.
Definieren eines Modells
In dieser exemplarischen Vorgehensweise können Sie ein Modell mithilfe von Code First oder EF Designer implementieren. Schließen Sie einen der beiden folgenden Abschnitte ab.
Option 1: Definieren eines Modells mithilfe von Code First
In diesem Abschnitt wird gezeigt, wie Sie ein Modell und die zugehörige Datenbank mithilfe von Code First erstellen. Fahren Sie mit dem nächsten Abschnitt (Option 2: Definieren eines Modells mithilfe von Database First) fort, wenn Sie lieber Database First verwenden möchten, um Ihr Modell mit Hilfe des EF-Designers aus einer Datenbank zurückzuentwickeln.
Bei der Code-First-Entwicklung beginnen Sie in der Regel mit dem Schreiben von .NET Framework-Klassen, die Ihr konzeptionelles Modell (Domänenmodell) definieren.
- Hinzufügen einer neuen Klasse zum WPFwithEFSample:
- Klicken Sie mit der rechten Maustaste auf den Projektnamen.
- Wählen Sie Hinzufügen und dann Neues Element aus.
- Wählen Sie Class aus, und geben Sie Product für den Klassennamen ein.
- Ersetzen Sie die Klassendefinition Product durch den folgenden Code:
namespace WPFwithEFSample
{
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 eine Category-Klasse mit der folgenden Definition hinzu:
using System.Collections.ObjectModel;
namespace WPFwithEFSample
{
public class Category
{
public Category()
{
this.Products = new ObservableCollection<Product>();
}
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ObservableCollection<Product> Products { get; private set; }
}
}
Die Products-Eigenschaft für die Category-Klasse und die Category-Eigenschaft für die Product-Klasse sind Navigationseigenschaften. Im Entity Framework bieten Navigationseigenschaften eine Möglichkeit, in einer Beziehung zwischen zwei Entitätstypen zu navigieren.
Zusätzlich zur Definition von Entitäten müssen Sie eine Klasse definieren, die von DbContext abgeleitet ist und DbSet<TEntity>-Eigenschaften bereitstellt. Die DbSet<TEntity>-Eigenschaften informieren den Kontext darüber, welche Typen Sie in das Modell einbeziehen möchten.
Eine Instanz des von DbContext abgeleiteten Typs verwaltet die Entitätsobjekte während der Laufzeit, was das Auffüllen der Objekte mit Daten aus einer Datenbank, die Änderungsnachverfolgung und das persistente Speichern von Daten in der Datenbank umfasst.
- Fügen Sie dem Projekt eine neue ProductContext-Klasse mit der folgenden Definition hinzu:
using System.Data.Entity;
namespace WPFwithEFSample
{
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 von Database First
In diesem Abschnitt wird gezeigt, wie Sie mit Database First Ihr Modell mithilfe des EF-Designers aus einer Datenbank zurückentwickeln 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
Wenn Sie auf eine bestehende Datenbank zugreifen, ist diese in der Regel bereits erstellt, aber für dieses Beispiel müssen wir eine Datenbank erstellen, auf die wir zugreifen können.
Der Datenbankserver, der mit Visual Studio installiert ist, unterscheidet sich je nach der installierten 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 nun 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.
Herstellen einer Verbindung mit LocalDB oder SQL Express, je nachdem, welches Sie installiert haben, und geben Sie Products 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
Zurückentwickeln (Reverse Engineering) des Modells
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 im linken Menü und dannADO.NET Entity Data Model aus.
Geben Sie als Name ProductModel ein, und klicken Sie auf OK.
Dadurch wird der Entity Data Model-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 es 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 Studio2010
Wenn Sie in Visual Studio 2010 arbeiten, müssen Sie den EF-Designer aktualisieren, um EF6-Codegenerierung zu verwenden.
- Klicken Sie mit der rechten Maustaste auf einen leeren Bereich Ihres Modells im EF Designer, 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 über einfache ICollection<T>-Eigenschaften verfügen. Beim Verwenden von WPF ist es jedoch wünschenswert, ObservableCollection für Sammlungseigenschaften zu verwenden, damit WPF Änderungen, die an den Auflistungen vorgenommen wurden, nachverfolgen kann. Zu diesem Zweck werden wir die Vorlagen so ändern, dass ObservableCollection verwendet wird.
Öffnen Sie den Projektmappen-Explorer, und suchen Sie die Datei ProductModel.edmx.
Suchen Sie die Datei ProductModel.tt, die unter der Datei „ProductModel.edmx“ geschachtelt ist.
Doppelklicken Sie auf die Datei „ProductModel.tt“, um sie im Visual Studio-Editor zu öffnen.
Suchen und ersetzen Sie die beiden Vorkommen von „ICollection“ durch „ObservableCollection“. Diese befinden sich etwa in den Zeilen 296 und 484.
Suchen und ersetzen Sie das erste Vorkommen von „HashSet-“ durch „ObservableCollection“. Dieses Vorkommen befindet sich etwa in Zeile 50. Ersetzen Sie nicht das zweite Vorkommen von „HashSet“, das später im Code zu finden ist.
Suchen und ersetzen Sie das einzige Vorkommen von „System.Collections.Generic“ durch „System.Collections.ObjectModel“. Dieses befindet sich etwa in Zeile 424.
Speichern Sie die Datei „ProductModel.tt“. 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 Datei „Category.cs“ (die unter ProductModel.tt geschachtelt ist) öffnen, sollten Sie sehen, dass die Products-Auflistung den Typ ObservableCollection<Product> hat.
Kompilieren Sie das Projekt.
Verzögertes Laden
Die Products-Eigenschaft für die Category-Klasse und die Category-Eigenschaft für die Product-Klasse sind Navigationseigenschaften. Im Entity Framework bieten Navigationseigenschaften eine Möglichkeit, in einer Beziehung zwischen zwei Entitätstypen zu navigieren.
EF bietet Ihnen die Möglichkeit, verwandte Entitäten aus der Datenbank automatisch zu laden, wenn Sie zum ersten Mal auf die Navigationseigenschaft zugreifen. Beachten Sie bei dieser Art von Laden (das als „Lazy Loading“ bezeichnet wird), dass beim ersten Zugriff auf jede Navigationseigenschaft eine separate Abfrage für die Datenbank ausgeführt wird, wenn sich die Inhalte nicht bereits im Kontext befinden.
Bei der Verwendung von POCO-Entitätstypen erreicht EF Lazy Loading, indem während der Laufzeit Instanzen abgeleiteter Proxytypen erstellt und dann virtuelle Eigenschaften in Ihren Klassen überschrieben werden, um den Ladehook hinzuzufügen. Um Lazy Loading von verwandten Objekten zu erzielen, müssen Sie Navigationseigenschaftsgetter als public und virtual deklarieren (Overridable in Visual Basic), und Ihre Klasse darf nicht sealed (NotOverridable in Visual Basic) sein. Wenn Database First verwendet wird, werden Navigationseigenschaften automatisch als virtuell festgelegt, um Lazy Loading zu ermöglichen. Im Abschnitt zu Code First haben wir uns entschieden, die Navigationseigenschaften aus demselben Grund virtuell zu machen.
Binden von Objekten an Steuerelemente
Fügen Sie die Klassen, die im Modell definiert sind, als Datenquellen für diese WPF-Anwendung hinzu.
Doppelklicken Sie im Projektmappen-Explorer auf MainWindow.xaml, um das Hauptformular zu öffnen.
Wählen Sie im Hauptmenü Projekt –> Neue Datenquelle hinzufügen… aus (in Visual Studio 2010 müssen Sie Daten –> Neue Datenquelle hinzufügen… auswählen).
Wählen Sie im Fenster „Datenquellentyp auswählen“ die Option Objekt aus, und klicken Sie dann auf Weiter.
Erweitern Sie im Dialogfeld „Datenobjekte auswählen“ das WPFwithEFSample zweimal, und wählen Sie Category aus.
Es ist nicht erforderlich, die Datenquelle Product auszuwählen, da wir über die Eigenschaft Product der Category-Datenquelle darauf zugreifen werden.Klicken Sie auf Fertig stellen.
Das Fenster „Datenquellen“ wird neben dem Fenster „MainWindow.xaml“ geöffnet. Wenn das Fenster „Datenquellen“" nicht angezeigt wird, wählen Sie Ansicht –> Andere Windows-> Datenquellen aus.
Klicken Sie auf das Anheftensymbol, sodass das Fenster „Datenquellen“ nicht automatisch ausgeblendet wird. Möglicherweise müssen Sie auf die Aktualisierungsschaltfläche klicken, wenn das Fenster bereits sichtbar war.
Wählen Sie die Datenquelle Category aus, und ziehen Sie sie auf das Formular.
Folgendes ist passiert, als wir diese Quelle gezogen haben:
- Die categoryViewSource-Ressource und das categoryDataGrid-Steuerelement wurden zu XAML hinzugefügt.
- Die DataContext-Eigenschaft für das übergeordnete Grid-Element wurde auf „{StaticResource categoryViewSource }“ festgelegt. Die categoryViewSource-Ressource dient als Bindungsquelle für das äußere\übergeordnete Grid-Element. Die inneren Grid-Elemente erben dann den DataContext-Wert vom übergeordneten Grid (die ItemsSource-Eigenschaft von categoryDataGrid ist auf „{Binding}“) festgelegt.
<Window.Resources>
<CollectionViewSource x:Key="categoryViewSource"
d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource categoryViewSource}">
<DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
ItemsSource="{Binding}" Margin="13,13,43,191"
RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}"
Header="Category Id" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}"
Header="Name" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Hinzufügen eines Detailrasters
Nachdem wir nun ein Raster zum Anzeigen von Kategorien haben, fügen wir ein Detailraster hinzu, um die zugehörigen Produkte anzuzeigen.
- Wählen Sie die Products-Eigenschaft unter der Datenquelle Category aus, und ziehen Sie sie auf das Formular.
- Die categoryProductsViewSource-Ressource und das productDataGrid-Raster werden zu XAML hinzugefügt.
- Der Bindungspfad für diese Ressource ist auf „Products“ festgelegt.
- Das WPF-Datenbindungsframework stellt sicher, dass nur Produkte im Zusammenhang mit der ausgewählten Kategorie in productDataGrid angezeigt werden.
- Ziehen Sie Button aus der Toolbox auf das Formular. Legen Sie die Eigenschaft Name auf buttonSave und die Eigenschaft Content auf Speichern fest.
Das Formular sollte ungefähr folgendermaßen aussehen:
Hinzufügen von Code, der die Dateninteraktion verarbeitet
Es ist an der Zeit, dem Hauptfenster einige Ereignishandler hinzuzufügen.
Klicken Sie im XAML-Fenster auf das Element <Window, um das Hauptfenster auszuwählen.
Wählen Sie im Fenster Eigenschaften oben rechts Ereignisse aus, und doppelklicken Sie dann auf das Textfeld rechts neben der Bezeichnung Geladen.
Fügen Sie außerdem das Click-Ereignis für die Schaltfläche Speichern hinzu, indem Sie im Designer auf die Schaltfläche „Speichern“ doppelklicken.
Damit gelangen Sie zum Code Behind für das Formular. Nun bearbeiten wir den Code so, dass ProductContext für den Datenzugriff verwendet wird. Aktualisieren Sie den Code für das MainWindow wie unten dargestellt.
Der Code deklariert eine Instanz von ProductContext mit langer Ausführungszeit. Das ProductContext-Objekt wird verwendet, um Daten abzufragen und in der Datenbank zu speichern. Dispose() für die ProductContext-Instanz wird dann von der überschriebenen OnClosing-Methode aufgerufen. Die Codekommentare enthalten Details zur Funktionsweise des Codes.
using System.Data.Entity;
using System.Linq;
using System.Windows;
namespace WPFwithEFSample
{
public partial class MainWindow : Window
{
private ProductContext _context = new ProductContext();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Data.CollectionViewSource categoryViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource")));
// Load is an extension method on IQueryable,
// defined in the System.Data.Entity namespace.
// This method enumerates the results of the query,
// similar to ToList but without creating a list.
// When used with Linq to Entities this method
// creates entity objects and adds them to the context.
_context.Categories.Load();
// After the data is loaded call the DbSet<T>.Local property
// to use the DbSet<T> as a binding source.
categoryViewSource.Source = _context.Categories.Local;
}
private void buttonSave_Click(object sender, RoutedEventArgs e)
{
// When you delete an object from the related entities collection
// (in this case Products), the Entity Framework doesn’t mark
// these child entities as deleted.
// Instead, it removes the relationship between the parent and the child
// by setting the parent reference to null.
// So we manually have to delete the products
// that have a Category reference set to null.
// The following code uses LINQ to Objects
// against the Local collection of Products.
// 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 use 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);
}
}
_context.SaveChanges();
// Refresh the grids so the database generated values show up.
this.categoryDataGrid.Items.Refresh();
this.productsDataGrid.Items.Refresh();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
}
Testen der WPF-Anwendung
Kompilieren Sie die Anwendung, und führen Sie sie aus. Wenn Sie Code First verwendet haben, sehen Sie, dass eine WPFwithEFSample.ProductContext-Datenbank für Sie erstellt wurde.
Geben Sie einen Kategorienamen im oberen Raster und Produktnamen im unteren Raster ein. Geben Sie nichts in ID-Spalten ein, da der Primärschlüssel von der Datenbank generiert wird.
Klicken Sie auf die Schaltfläche Speichern, um die Daten in der Datenbank zu speichern.
Nach dem Aufruf von SaveChanges() von DbContext werden die IDs mit den generierten Werten der Datenbank aufgefüllt. Da wir Refresh() nach SaveChanges() aufgerufen haben, werden die DataGrid-Steuerelemente ebenfalls mit den neuen Werten aktualisiert.
Weitere Ressourcen
Weitere Informationen zur Datenbindung an Sammlungen mit WPF finden Sie in diesem Thema in der WPF-Dokumentation.