Datenbindung mit Windows Forms

In dieser schrittweisen exemplarischen Vorgehensweise wird gezeigt, wie POCO-Typen an Window Forms -Steuerelemente (WinForms) 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 zu vielen 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 führt dazu, dass die Detailansicht mit den entsprechenden untergeordneten Daten aktualisiert wird.

Die Screenshots und Codeeinträge in dieser exemplarischen Vorgehensweise werden von Visual Studio 2013 entfernt, aber Sie können diese exemplarische Vorgehensweise mit Visual Studio 2012 oder Visual Studio 2010 abschließen.

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 ->> Project....
  • Wählen Sie Windows im linken Bereich und Windows FormsApplication im rechten Bereich aus.
  • Geben Sie WinFormswithEFSample als Namen ein.
  • Klicken Sie auf OK.

Installieren des Entity Framework NuGet-Pakets

  • Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das WinFormswithEFSample-Projekt
  • Wählen Sie "NuGet-Pakete verwalten" aus...
  • Wählen Sie im Dialogfeld "NuGet-Pakete verwalten" die Registerkarte "Online " aus, und wählen Sie 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 entfernt, wenn das EntityFramework-Paket installiert ist. Die System.Data.Entity-Assembly wird nicht mehr für Entity Framework 6-Anwendungen verwendet.

Implementieren von IListSource für Sammlungen

Auflistungseigenschaften müssen die IListSource-Schnittstelle implementieren, um die Zwei-Wege-Datenbindung mit der Sortierung bei verwendung von Windows Forms zu aktivieren. Dazu erweitern wir ObservableCollection, um IListSource-Funktionalität hinzuzufügen.

  • Fügen Sie dem Projekt eine ObservableListSource-Klasse hinzu:
    • Klicken Sie mit der rechten Maustaste auf den Projektnamen
    • Hinzufügen auswählen –> Neues Element
    • 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 Zwei-Wege-Datenbindung sowie die Sortierung. Die Klasse abgeleitet von ObservableCollection<T> und fügt eine explizite Implementierung von IListSource hinzu. Die GetList()-Methode von IListSource wird implementiert, um eine IBindingList-Implementierung zurückzugeben, die mit der 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 zugeordnete Datenbank mithilfe von Code First erstellen. Wechseln Sie zum nächsten Abschnitt (Option 2: Definieren eines Modells mithilfe von Database First), wenn Sie das Modell zuerst verwenden möchten, um Ihr Modell mithilfe des EF-Designers zu reversen.

Bei Verwendung der Code First-Entwicklung beginnen Sie in der Regel, .NET Framework Klassen zu schreiben, die Ihr konzeptionelles (Domänenmodell) 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 Kategorieklasse 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 sie im Modell einschließen möchten. Die DbContext - und DbSet-Typen werden in der EntityFramework-Assembly definiert.

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 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 von Database First

In diesem Abschnitt wird gezeigt, wie Sie das Modell mithilfe des EF-Designers mithilfe des EF-Designers verwenden. Wenn Sie den vorherigen Abschnitt abgeschlossen haben (Option 1: Definieren eines Modells mit Code First), überspringen Sie diesen Abschnitt, und wechseln Sie direkt zum Abschnitt " Lazy Laden ".

Erstellen einer vorhandenen Datenbank

Normalerweise, 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 Mit Visual Studio installierte Datenbankserver unterscheidet sich je nach version von Visual Studio, die Sie installiert haben:

  • Wenn Sie Visual Studio 2010 verwenden, erstellen Sie eine SQL Express-Datenbank.
  • Wenn Sie Visual Studio 2012 verwenden, erstellen Sie eine LocalDB-Datenbank .

Gehen wir weiter, und generieren Sie die Datenbank.

  • Ansicht –> Server-Explorer

  • Klicken Sie mit der rechten Maustaste auf Datenverbindungen –> Verbindung hinzufügen...

  • Wenn Sie nicht mit einer Datenbank aus dem Server-Explorer verbunden sind, müssen Sie Microsoft SQL Server als Datenquelle auswählen.

    Datenquelle ändern

  • Stellen Sie eine Verbindung mit LocalDB oder SQL Express her, je nachdem, welche Sie installiert haben, und geben Sie Produkte als Datenbanknamen ein.

    Hinzufügen einer LocalDB-Verbindung

    Hinzufügen von Verbindungs express

  • Wählen Sie "OK" aus, und Sie werden gefragt, ob Sie eine neue Datenbank erstellen möchten, wählen Sie "Ja" aus.

    Datenbank erstellen

  • Die neue Datenbank wird jetzt im Server-Explorer angezeigt, klicken Sie mit der rechten Maustaste darauf, und wählen Sie "Neue Abfrage" aus.

  • Kopieren Sie die folgende SQL in die neue Abfrage, und 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 werden den Entity Framework Designer verwenden, der im Rahmen von Visual Studio enthalten ist, um unser Modell zu erstellen.

  • Project –> Neues Element hinzufügen...

  • Wählen Sie "Daten " im linken Menü aus, und ADO.NET Entitätsdatenmodell

  • 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"

    Auswählen von Modellinhalten

  • 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".

    Wählen Sie Ihre Verbindung aus

  • Klicken Sie auf das Kontrollkästchen neben 'Tabellen', um alle Tabellen zu importieren, und klicken Sie auf 'Fertig stellen'

    Wählen Sie Ihre Objekte aus.

Sobald der Reverse Engineering-Prozess abgeschlossen ist, wird dem Projekt das neue Modell hinzugefügt und für Sie geöffnet, um im Entity Framework Designer anzuzeigen. Eine App.config Datei wurde ihrem Projekt auch mit den Verbindungsdetails für die Datenbank hinzugefügt.

Weitere Schritte in Visual Studio 2010

Wenn Sie in Visual Studio 2010 arbeiten, müssen Sie den EF-Designer aktualisieren, um die EF6-Codegenerierung zu verwenden.

  • Klicken Sie mit der rechten Maustaste auf einen leeren Punkt Ihres Modells im EF Designer, und wählen Sie "Codegenerierungselement hinzufügen" aus...
  • Wählen Sie Onlinevorlagen im linken Menü 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 mit Visual Studio gelieferten Vorlagen oder heruntergeladene Vorlagen aus dem Visual Studio-Katalog sind für die allgemeine Verwendung vorgesehen. Dies bedeutet, dass die von diesen Vorlagen generierten Entitäten einfache ICollection<T-Eigenschaften> aufweisen. Bei der Datenbindung ist es jedoch wünschenswert, Auflistungseigenschaften 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 die datei "ProductModel.edmx" Projektmappen-Explorer, und suchen Sie die Datei "ProductModel.edmx"

  • Suchen Sie die ProductModel.tt Datei, die unter der Datei ProductModel.edmx geschachtelt wird.

    Produktmodellvorlage

  • 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 in den Linien 296 und 484.

  • Suchen und ersetzen Sie das erste Vorkommen von "HashSet" durch "ObservableListSource". Dieses Vorkommen befindet sich in ca. 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 erstellt wird, klicken Sie mit der rechten Maustaste auf ProductModel.tt, und wählen Sie "Benutzerdefiniertes Tool ausführen" aus.

Wenn Sie jetzt die Datei "Category.cs" (die unter ProductModel.tt geschachtelt ist) öffnen, sollten Sie sehen, dass die Products-Auflistung den Typ "ObservableListSource<Product>" aufweist.

Kompilieren Sie das Projekt.

Lazy Loading

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 beim ersten Zugriff auf die Navigationseigenschaft zu laden. 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.

Beim Verwenden von POCO-Entitätstypen erreicht EF das lazy Laden, indem Instanzen abgeleiteter Proxytypen während der Laufzeit erstellt und dann virtuelle Eigenschaften in Ihren Klassen außer Kraft gesetzt werden, um den Ladehaken hinzuzufügen. Um das laden von verwandten Objekten zu erhalten, müssen Sie Navigationseigenschafts-Getters als öffentliche und virtuelle (Überschreibung in Visual Basic) deklarieren, und Die Klasse darf nicht versiegelt werden (NotOverridable in Visual Basic). Beim Verwenden von Datenbank-First-Navigationseigenschaften werden automatisch virtuelle Elemente erstellt, um das Laden zu ermöglichen. Im Abschnitt "Code First" haben wir beschlossen, die Navigationseigenschaften aus demselben Grund virtuellen zu machen.

Binden von Objekten an Steuerelemente

Fügen Sie die Klassen hinzu, die im Modell als Datenquellen für diese WinForms-Anwendung definiert sind.

  • Wählen Sie im Hauptmenü Project -> Neue Datenquelle hinzufügen ... (in Visual Studio 2010 müssen Sie Daten > auswählen – Neue Datenquelle hinzufügen...)

  • Wählen Sie im Fenster "Datenquellentyp auswählen" das Objekt aus, und klicken Sie auf "Weiter".

  • Im Dialogfeld "Datenobjekte auswählen" können Sie die WinFormswithEFSample zweimal auswerten, und wählen Sie "Kategorie " aus, da die Datenquelle "Produkt" nicht ausgewählt werden muss, da wir die Eigenschaft des Produkts in der Kategoriedatenquelle durchlaufen.

    Data Source

  • Klicken Sie auf "Fertig stellen". Wenn das Datenquellenfenster nicht angezeigt wird, wählen Sie "Ansicht "> Andere Windows- Datenquellen"> aus.

  • Drücken Sie das Pin-Symbol, sodass das Fenster "Datenquellen" nicht automatisch ausgeblendet wird. Möglicherweise müssen Sie auf die Schaltfläche "Aktualisierung" klicken, wenn das Fenster bereits sichtbar war.

    Datenquelle 2

  • Doppelklicken Sie in Projektmappen-Explorer auf die Datei Form1.cs, um das Hauptformular im Designer zu öffnen.

  • Wählen Sie die Datenquelle "Kategorie " aus, und ziehen Sie ihn auf das Formular. Standardmäßig werden dem Designer ein neues DataGridView -Steuerelement (categoryDataGridView) und Navigationssymbolleistensteuerelemente 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 KategorieDataGridView. Wir möchten die Spalte "CategoryId" auf schreibgeschützt festlegen. Der Wert für die CategoryId-Eigenschaft wird von der Datenbank generiert, nachdem wir die Daten gespeichert haben.

    • 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 ihn auf das Formular. Die productDataGridView und productBindingSource werden dem Formular hinzugefügt.

  • Bearbeiten Sie die Spalten im ProductDataGridView. Wir möchten die Spalten "CategoryId" und "Kategorie" ausblenden und ProductId auf schreibgeschützt festlegen. Der Wert für die ProductId-Eigenschaft wird von der Datenbank generiert, nachdem wir die Daten gespeichert haben.

    • 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 dasselbe 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 CodeBehinding CategoryBindingSource.DataSource auf die Auflistung von Entitäten hinzu, die derzeit von DbContext nachverfolgt werden. Wenn wir Produkte aus der Kategorie gezogen und gelöscht haben, kümmerte sich die WinForms darum, die ProductsBindingSource.DataSource-Eigenschaft auf "CategoryBindingSource" und "productsBindingSource.DataMember"-Eigenschaft auf "Products" einzurichten. Aufgrund dieser Bindung werden nur die Produkte, die zur aktuell ausgewählten Kategorie gehören, im ProductDataGridView angezeigt.

  • Aktivieren Sie die Schaltfläche "Speichern " auf der Navigationssymbolleiste, indem Sie auf die rechte Maustaste klicken, und wählen Sie "Aktiviert" aus.

    Formular 1 Designer

  • 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 zum Code hinter dem Formular bringen. Der Code für den categoryBindingNavigatorSaveItem_Click Ereignishandler wird im nächsten Abschnitt hinzugefügt.

Fügen Sie den Code hinzu, 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 deklariert eine lang ausgeführte 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 aus der überschriebenen OnClosing-Methode aufgerufen. Die Codekommentare geben Details zu dem, was der Code tut.

    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 und Ausführen der Anwendung, und Sie können die Funktionalität testen.

    Formular 1 vor dem Speichern

  • Nach dem Speichern der generierten Schlüssel wird auf dem Bildschirm angezeigt.

    Formular 1 nach dem Speichern

  • Wenn Sie Code zuerst verwendet haben, sehen Sie auch, dass für Sie eine WinFormswithEFSample.ProductContext-Datenbank erstellt wird.

    Server Objekt-Explorer