MSDN Magazin > Home > Ausgaben > 2007 > February >  Datenpunkte: Datengebundene Anwendungen mit ADO...
Datenpunkte
Datengebundene Anwendungen mit ADO.NET und benutzerdefinierten Objekten
John Papa

Codedownload verfügbar unter: DataPoints2007_02.exe (174 KB)
Browse the Code Online
Die Bindungssteuerelemente in Windows Forms weisen gegenüber den Steuerelementen zur Datenbindung der Vergangenheit erhebliche Verbesserungen auf. Sie ermöglichen Ihnen einen schnellen Einstieg und führen die mit dem Erstellen von Formularen verbundenen redundanten Aufgaben aus. Das Verhalten der Steuerelemente können Sie in hohem Maße anpassen und erweitern. Daten können in verschiedenen Containern, wie DataSets und benutzerdefinierten Klassenentitäten, transportiert werden. Die Bindungstools von Windows® Forms ermöglichen Ihnen, zu allen diesen Objekttypen eine Bindung herzustellen. Wenn Sie kein DataSet verwenden möchten, können Sie benutzerdefinierte Entitäten erstellen, die als Datenspeicher für Ihre Anwendung dienen können. Ebenso können Sie List<T> und andere Auflistungsstypen verwenden, um einen Satz an benutzerdefinierten Entitäten zu speichern. Diese benutzerdefinierten Entitätstypen können mithilfe von BindingSource und BindingNavigator einfach gebunden werden. In diesem Artikel erfahren Sie, wie Sie eine benutzerdefinierte Liste mit Geschäftseinheiten mithilfe der Bindungstools in Microsoft® .NET Framework 2.0 binden können. Zu diesem Zweck wird eine voll funktionsfähige datengesteuerte Windows Forms-Anwendung geschrieben.
Als Erstes wird in den folgenden Ausführungen die Anwendung beschrieben. Dabei steht die Verwendung der Bindungssteuerelemente DataGridView, BindingSource und BindingNavigator im Mittelpunkt. Anschließend werden Sie schrittweise durch die untergeordneten Ebenen geführt und erhalten einen Überblick über deren Architektur. Darüber hinaus erhalten Sie Informationen über das Abrufen, das Speichern, den Zugriff auf und das Zurückschicken von Daten zur Datenbank. Die Downloaddatei dieser Ausgabe enthält den gesamten Code für die Beispielanwendung.

Testen der Anwendung
Die Anwendung ermöglicht es dem Benutzer, Datensätze anzuzeigen, hinzuzufügen, zu löschen, zu aktualisieren und zu suchen sowie durch Datensätze zu navigieren. Es werden Northwind-Auftragsdaten in DataGridView geladen, wie in Abbildung 1 zu sehen ist. Wenn ein Auftrag ausgewählt wurde, werden die Textfelder (TextBox), die Kombinationsfelder (ComboBox) und die anderen Steuerelemente auf der rechten Seite des Formulars mit den ausgewählten Auftragsdaten gefüllt. Alle Steuerelemente sind über ein BindingSource-Steuerelement an die gleiche Datenquelle gebunden.
Abbildung 1 Anzeigen von Northwind-Aufträgen in DataGridView (Klicken Sie zum Vergrößern auf das Bild)
Das BindingNavigator-Steuerelement wird durch die Symbolleiste am oberen Formularrand in Abbildung 1 dargestellt. Sie enthält Standardnavigationsschaltflächen zum Ändern des auf dem Bildschirm angezeigten Auftragsdatensatzes. Die Navigationsschaltflächen funktionieren in Kombination mit dem Raster auf der linken Seite, sodass sie mit dem aktuellen Datensatz synchron bleiben können. Die Symbolleiste besitzt zudem Schaltflächen, die Ereignishandler zum Hinzufügen, Löschen und Aktualisieren von Auftragsinformationen ausführen. Schließlich können Sie mithilfe der Anwendung nach einem bestimmten Auftrag suchen (achten Sie auf das Fernglas).
Die Felder für den Auftragsdatensatz, die Fremdschlüsselverweise darstellen, können mit den Kombinationsfeld-Steuerelementen angezeigt werden. Ein Kombinationsfeld könnte beispielsweise verwendet werden, um eine Liste der Vertriebsmitarbeiter anzuzeigen. Der Vertriebsmitarbeiter für einen bestimmten Auftrag wird im Kombinationsfeld ausgewählt. Dies stellt eine bessere Option als das Anzeigen der Mitarbeiter ID dar, die der Benutzer der Anwendung wahrscheinlich nicht deuten kann. In Abbildung 1 sehen Sie, dass in einem Kombinationsfeld der Mitarbeitername anstatt der Mitarbeiter ID angezeigt wird. Der Kundename wird ebenso in einem Kombinationsfeld angezeigt.

Implementieren von benutzerdefinierten Entitäten und Schnittstellen
Das DataSet stellt ein leistungsfähiges Tool im Datenzugriffsarsenal dar. Eine weitere leistungsstarke Möglichkeit besteht in der Verwendung benutzerdefinierter Klassen zum Verwalten und Darstellen des Datenmodells in einer Anwendung. Die jeweiligen Vor- und Nachteile sind ausführlich dokumentiert. Viele Benutzer sind entweder überzeugte Vertreter von DataSets oder von benutzerdefinierten Klassen. Im Grunde genommen sind beide Methoden für Unternehmensarchitekturen geeignet. Des Weiteren funktionieren ADO.NET-Tools sowohl mit DataSets als auch mit benutzerdefinierten Klassen, um zum Darstellen der Datenobjekte Entitäten zu erstellen. In erster Linie benötigen Sie einen Datenspeicher für Ihre Daten. In dieser Anwendung werde ich benutzerdefinierte Entitäten verwenden.
Die Beispielanwendung umfasst zwei Projekte: Eines für die Darstellung der Daten und ein zweites für die Geschäftslogik und den Datenzugriff. Beim Erstellen einer benutzerdefinierten Entität auf der unteren Ebene müssen Sie die Eigenschaften für die Entität erstellen. Beispielsweise hat die Customer-Klasse eine CustomerID-Eigenschaft und eine CompanyName-Eigenschaft. Abbildung 2 zeigt die privaten Felder und die öffentlichen Eigenschaften, die die CustomerID und den CompanyName darstellen. Während die Eingabe dieses Codes insbesondere im Vergleich zur Verwendung eines DataSets mühsam sein kann, können Sie mit wenigen Maßnahmen das Erstellen der Klasse erheblich erleichtern. Dazu gehören die Verwendung der vielen Umgestaltungstools, mit denen Eigenschaften umgehend erstellt werden können, sowie ein Codegenerierungstool zum Generieren der gesamten Klasse.
public event PropertyChangedEventHandler PropertyChanged;

private string _CustomerID;
public string CustomerID
{
    get { return _CustomerID; } set { _CustomerID = value; }
}

private string _CompanyName;
public string CompanyName
{
    get { return _CompanyName; }
    set
    {
        if (!value.Equals(_CompanyName))
        {
            _CompanyName = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, 
                    new PropertyChangedEventArgs("CompanyName"));
            }
        }
    }
}

Betrachten Sie den Code in Abbildung 2. Die Customer-Klasse in diesem Beispiel implementiert die INotifyPropertyChanged-Schnittstelle, die ein Ereignis mit dem Namen „PropertyChanged“ aufruft, wenn die CompanyName-Eigenschaft geändert wird. Beachten Sie, dass der Set-Accessor der CompanyName-Eigenschaft vor dem Festlegen des neuen Werts prüft, ob der Wert tatsächlich geändert wurde. In diesem Fall wird das PropertyChanged-Ereignis aufgerufen, und die Klasse kann alle benachrichtigen, die auf die Änderung warten. In meiner Anwendung aktualisiert BindingSource automatisch die Steuerelemente auf dem Formular mit dem neuen Wert, wenn eine Benachrichtigung über eine Änderung eintrifft.
Die Anwendung enthält zudem drei Entitätsklassen: Order, Customer und Employee. Alle implementieren die INotifyPropertyChanged-Schnittstelle und enthalten Eigenschaftsaccessoren, die das Abrufen und Festlegen von Werten für die Eigenschaften verarbeiten.

Generika und ADO.NET
Nach dem Erstellen der Entitäten müssen Methoden erstellt werden, mit denen sie abgerufen und gespeichert werden. Die Entitäten der Anwendung implementieren eine Standardliste statischer Methoden. Sie kann aus einigen oder allen der folgenden Methoden bestehen: GetEntity, GetEntityList, SaveEntity und DeleteEntity.
Sie können eine Klasse für die Entitäten und eine separate Klasse mit den Datenzugriffsmethoden erstellen. Im Allgemeinen empfiehlt es sich, diese Klassen nur zu trennen, wenn die Architektur vorschreibt, dass die Entitäten nicht eng mit den Methoden verknüpft sind, die sie speichern und abrufen. In dieser Beispielanwendung sind die Methoden eng verknüpft. Deshalb wurden sie in einer einzigen Klasse zusammengefasst und statisch gemacht.
Abbildung 3 zeigt die GetEntityList<Order>-Methode. Sie gibt eine Auftragsentitätenliste zurück, die alle Aufträge in der Northwind Datenbank darstellt. (Selbstverständlich könnte sie gefiltert werden, wenn Sie einen Parameter für Aufträge für einen bestimmten Kunden oder einen Datumsbereich hinzufügen würden.) Durch die Verwendung von Generika und die Rückgabe einer List<T> anstatt einer ArrayList garantiert der Code, dass jedes Objekt in der Liste vom Typ T ist. Dies bedeutet auch, dass Sie auf die Eigenschaften einer Entität innerhalb von List<T> zugreifen können, ohne sie in den Typ der Entität umzuwandeln. Diese Umwandlung wäre jedoch notwendig, wenn die Entität in einer spezifischen Liste gespeichert wäre. Sie könnten beispielsweise die OrderID aus dem ersten Auftrag in der Liste mithilfe des folgenden Codes abrufen:
List<Order> orderList = GetMyListOfOrders();
int orderID = orderList[0].OrderID;
public static List<Order> GetEntityList()
{
    List<Order> OrderList = new List<Order>();

    using (SqlConnection cn = new SqlConnection(Common.ConnectionString))
    {
        string proc = "pr_Order_GetList";
        using (SqlCommand cmd = new SqlCommand(proc, cn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cn.Open();
            SqlDataReader rdr = cmd.ExecuteReader(
                CommandBehavior.CloseConnection);
            while (rdr.Read())
            {
                Order order = FillOrder(rdr);
                OrderList.Add(order);
            }
            if (!rdr.IsClosed) rdr.Close();
        }
    }
    return OrderList;
}

Wenn die Entitäten in einer spezifischen Liste gespeichert wären, müsste das Objekt in den Typ umgewandelt werden:
ArrayList orderList = GetMyListOfOrders();
int orderID = ((Order)orderList[0])).OrderID;
Es wäre möglich gewesen, ein DataSet oder eine DataTable zu erstellen und mit den Aufträgen zu füllen. Hier wurde jedoch wegen des schnelleren Zugriffs auf die Daten SqlDataReader verwendet. Wie Sie Abbildung 3 entnehmen können, werden die Daten über SqlDataReader für jede Zeile abgerufen, die eine Order-Entität instanziiert und füllt. Anschließend wird diese Entität zu List<Order> hinzugefügt, und der Prozess wird für jede Zeile in SqlDataReader wiederholt. Alternativ hätten Sie eine DataTable verwenden und die DataRows durchlaufen können. Der Leistungsunterschied wäre geringfügig. In diesem Fall besteht jedoch kein Vorteil darin, den zusätzlichen Aufwand einer DataTable in Kauf zu nehmen, da lediglich die Zeilen durchlaufen werden und die eigene benutzerdefinierte Entitätenliste gefüllt wird. Die FillOrder-Methode führt den folgenden Code aus, der die Instanz von Order erstellt und die Eigenschaften aus SqlDataReader setzt:
Order order = new Order();
order.OrderID = Convert.ToInt32(rdr["OrderID"]);
order.CustomerID = rdr["CustomerID"].ToString();
order.EmployeeID = Convert.ToInt32(rdr["EmployeeID"]);
order.OrderDate = Convert.ToDateTime(rdr["OrderDate"]);
order.ShipVia = rdr["ShipVia"].ToString();
order.ShipName = rdr["ShipName"].ToString();
order.ShipAddress = rdr["ShipAddress"].ToString();
order.ShipCity = rdr["ShipCity"].ToString();
order.ShipCountry = rdr["ShipCountry"].ToString();
return order;
Beachten Sie, dass in Abbildung 3 CommandBehavior.CloseConnection an die ExecuteReader-Methode übergeben wurde. Dadurch wird das SqlConnection-Objekt geschlossen, sobald SqlDataReader geschlossen wird.
Die Entitäten enthalten ebenfalls statische Methoden, mit denen Daten eingefügt, aktualisiert und gelöscht werden können. Ich habe eine öffentliche statische Methode mit dem Namen „SaveEntity“ erstellt, die eine Entität akzeptiert und festlegt, ob sie neu oder bereits vorhanden ist. Anschließend ruft sie die entsprechende gespeicherte Prozedur auf, um die Aktionsabfrage durchzuführen. Die statische AddEntity (siehe Abbildung 4) für die Customer-Klasse akzeptiert die Order-Entität und ordnet dann die Werte der Entität den entsprechenden Parametern der gespeicherten Prozedur zu.
private static Order AddEntity(Order order)
{
    int orderID = 0;
    using (SqlConnection cn = new SqlConnection(Common.ConnectionString))
    {
        string proc = "pr_Order_Add";
        using (SqlCommand cmd = new SqlCommand(proc, cn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@customerID", order.CustomerID);
            cmd.Parameters.AddWithValue("@employeeID", order.EmployeeID);
            cmd.Parameters.AddWithValue("@orderDate", order.OrderDate);
            cmd.Parameters.AddWithValue("@shipVia", order.ShipVia);
            cmd.Parameters.AddWithValue("@shipName", 
                GetValue(order.ShipName));
            cmd.Parameters.AddWithValue("@shipAddress", 
                GetValue(order.ShipAddress));
            cmd.Parameters.AddWithValue("@shipCity", 
                GetValue(order.ShipCity));
            cmd.Parameters.AddWithValue("@shipCountry", 
                GetValue(order.ShipCountry));
            cmd.Parameters.Add(new SqlParameter("@orderID", 
                SqlDbType.Int));
            cmd.Parameters["@orderID"].Direction = 
                ParameterDirection.Output;
            cn.Open();
            cmd.ExecuteNonQuery();
            orderID = Convert.ToInt32(cmd.Parameters["@orderID"].Value);
        }
        order = GetEntity(orderID);
    }
    return order;
}

Sowohl ADO.NET als auch die benutzerdefinierten Entitäten sind wichtige Bestandteile dieser Architektur. Die Abrufmethoden verwenden ADO.NET zum Abrufen der Daten aus der Datenbank. Die benutzerdefinierten Entitäten werden anschließend gefüllt und an die Präsentationsebene zurückgegeben. Die Methoden „SaveEntity“ und „DeleteEntity“ akzeptieren benutzerdefinierte Entitäten und entnehmen ihnen danach Werte, um die Änderungen in der Datenbank anzuwenden.

Datenquellen
Oben haben Sie erfahren, wie benutzerdefinierte Entitäten erstellt, gefüllt und zurückgegeben werden. Als Nächstes wird die Präsentationsebene erläutert. Weil sich die Customer-, Order- und Employee-Klassen im Klassenbibliotheksprojekt befinden, können sie zur Erstellung der gebundenen Steuerelemente auf einem Formular verwendet werden. Das Fenster „Datenquellen“ zeigt alle Datenquellen an, die für das Projekt verfügbar sind. In Abbildung 5 können Sie erkennen, dass die drei im Klassenbibliotheksprojekt erstellten Entitäten zum Fenster Datenquelle des UI-Projekts hinzugefügt wurden.
Abbildung 5 Fenster „Datenquelle“ 
Datenquellen können von einem Webdienst, einem Objekt oder einer Datenbank abgerufen werden. In diesem Fall wurden Objektdatenquellen hinzugefügt, da Klassenentitäten verwendet werden. Die Datenquelle wurde mithilfe ihres Assistenten hinzugefügt. Dieser fordert Sie auf, den Namespace und die Klasse auszuwählen, die Sie als Datenquelle hinzufügen möchten. Wenn Sie bereits auf das Klassenbibliotheksprojekt verweisen, werden die Klassen „Customer“, „Employee“ und „Order“ angezeigt.
Nachdem die Datenquellen hinzugefügt wurden, werden sie im Fenster „Datenquellen“ angezeigt. Die Datenquelle Order in Abbildung 5 zeigt alle öffentlichen Eigenschaften der Order-Klasse. Neben jedem Eigenschaftennamen befindet sich ein Symbol, das den Steuerelementtyp darstellt, der für die Anzeige jedes Eigenschaftenwerts verwendet wird. Die OrderDate-Eigenschaft stellt ein DateTimePicker-Steuerelement dar, und die ShipAddress stellt ein Textfeld-Steuerelement dar. Diese Steuerelemente können durch Klicken auf den Eigenschaftennamen im Fenster „Datenquellen“ und durch Auswählen eines anderen Steuerelements aus der Dropdownliste geändert werden. Ich habe beispielsweise das Steuerelement für die OrderID-Eigenschaft in ein Bezeichnungsfeld-Steuerelement geändert, damit es schreibgeschützt ist.
Eigenschaften, die Referenzfelder zu anderen Entitäten sind, wie z. B. die Order.CustomerID-Eigenschaft, sollen hier nicht direkt angezeigt werden. Stattdessen soll ein geeigneterer Beschreibungswert für sie angezeigt werden. So wurde beispielsweise der Steuerelementtyp für die CustomerID-Eigenschaft in ein Kombinationsfeld geändert, damit die Liste mit allen Kundendaten gefüllt und die entsprechende CompanyName-Eigenschaft des Kunden angezeigt werden kann. Es wurden sowohl die Eigenschaft „CustomerID“ als auch die Eigenschaft „EmployeeID“ in Kombinationsfelder geändert. Sie können auch angeben, dass keine Eigenschaft auf dem Formular angezeigt werden soll, indem Sie anstatt eines Steuerelementtyps die Option [Keine] aus der Liste auswählen.
Nach dem Festlegen der Eigenschaften sowie der Steuerelemente, in denen sie angezeigt werden sollen, klicken Sie im Fenster „Datenquellen“ auf die Order-Entität, und wählen Sie in der Liste die Option „Details“ aus. Dies ermöglicht Ihnen, die Datenquelle „Order“ in ein Formular zu ziehen und sowohl die Anzeigensteuerelemente als auch die Bindungssteuerelemente (BindingSource und BindingNavigator) auf dem Formular automatisch zu generieren. So wurden die Steuerelemente auf der rechten Seite des Formulars in Abbildung 1 erstellt.

Bindungssteuerelemente
BindingSource ist ein nicht sichtbares Steuerelement, das die Verbindung zwischen der Order-Entität und den Steuerelementen auf dem Formular darstellt. Die aktuelle Order-Entität in BindingSource wird in den Steuerelementen auf der rechten Seite des Bildschirms angezeigt, wo sie angezeigt und bearbeitet werden können. In dieser Anwendung wird sie an eine List<Order>-Entität gebunden, die aus der statischen Methode der Klasse „Order“ übergeben wird.
Das BindingNavigator-Steuerelement wurde ebenso durch Ziehen der Datenquelle Order in das Formular erstellt. Dieses Steuerelement zeigt am oberen Formularrand eine Symbolleiste an, die standardmäßig über einige Schaltflächen zum Hinzufügen, Speichern und Löschen von Datensätzen sowie über Navigationsschaltflächen verfügt. BindingNavigator ist mit dem BindingSource-Steuerelement synchron, sodass das zweite Steuerelement automatisch die Positionsänderung widerspiegelt, wenn ein Datensatz in einem der Steuerelemente neu positioniert wird. Wenn beispielsweise der Benutzer im BindingNavigator-Steuerelement auf die Schaltfläche „Weiter“ klickt, ändert das BindingSource-Steuerelement seine aktuelle Entität zur nächsten Entität. Als Folge zeigen die Steuerelemente auf der rechten Seite die aktuellen Eigenschaftenwerte der Entität an.
Die Schaltflächen von BindingNavigator sind ebenso Steuerelemente. Folglich verfügen sie auch über Eigenschaften und Ereignisse, die Sie festlegen können. Ich habe für die Schaltflächen „Speichern“ und „Löschen“ auf der Symbolleiste Ereignishandler hinzugefügt, damit ich eine Überprüfung und andere Logik hinzufügen konnte, bevor geänderte Daten über die vorher erstellen statischen Datenzugriffsmethoden der Entitäten persistent in der Datenbank gespeichert wurden.
Auch wenn das BindingNavigator-Steuerelement Datensatzbewegungen verarbeitet und eine einfache Methode zum Speichern von Datenänderungen bereitstellt, müssen Sie es nicht unbedingt einfügen. Sobald das Steuerelement durch Ziehen einer Datenquelle ins Formular automatisch erstellt wird, können Sie es aus dem Formular ohne weitere Auswirkungen löschen. Selbstverständlich müssen Sie anschließend Ihren eigenen Code schreiben, um Bewegungen in der Auftragsliste und das Speichern von Änderungen zu implementieren. Die Notwendigkeit dieses Vorgangs hängt von den Anforderungen Ihrer Benutzeroberfläche ab.
Das CustomerID-Kombinationsfeld ist derzeit an die CustomerID-Eigenschaft der Order-Datenquelle gebunden (über das BindingSource-Steuerelement). Jetzt müssen Sie noch das Kombinationsfeld mit einer Liste von Kunden füllen und den CompanyName des Kunden anzeigen. Beim Erstellen des Klassenbibliotheksprojekts wurde eine Customer-Entität erstellt und eine GetEntityList<Customer>-Methode daraus verfügbar gemacht. Anschließend wurde eine Datenquelle für die Customer-Entität hinzugefügt. Nun muss lediglich die Customer-Datenquelle über das CustomerID-Kombinationsfeld gezogen und abgelegt werden. Dadurch wird ein zweites BindingSource-Steuerelement erstellt. Das Kombinationsfeld verwendet diese neue BindingSource, um die Liste der Kunden für die Anzeige zu laden. Deshalb ist der ausgewählte Wert des Kombinationsfeldes noch an das BindingSource-Steuerelement von Order gebunden. Zudem wird die Liste der Kunden mit ihren CompanyName-Eigenschaften im Steuerelement angezeigt. Dieser Prozess wird wiederholt, um auch die Liste für das Employee-Kombinationsfeld zu füllen. Durch Ablegen der Datenquelle auf dem Kombinationsfeld wurde die Information gespeichert, dass kein weiteres BindingNavigator-Steuerelement benötigt wurde.
Es war jedoch wünschenswert, Benutzern neben der Symbolleiste von BindingNavigator eine zweite Möglichkeit zur Verfügung zu stellen, durch die Auftragsdatensätze zu navigieren. Also habe ich im Fenster „Datenquellen“ auf die Datenquelle „Order“ geklickt, DataGridView ausgewählt und die Datenquelle „Order“ auf das Formular gezogen. Dadurch wurde ein DataGridView-Steuerelement auf dem Formular erstellt, das eine Spalte mit jeder Eigenschaft der Order-Entität enthielt. Ich habe anschließend die meisten Spalten entfernt, sodass nur die Hauptinformationen im Raster angezeigt werden. Es wurden keine zusätzlichen Bindungssteuerelemente erstellt, weil sich bereits ein BindingSource-Steuerelement auf diesem Formular befindet, das die Order-Datenquelle bindet. Zu diesem Zeitpunkt ist der aktuelle Auftrag für alle Steuerelemente identisch, da alle mit dem BindingSource-Steuerelement von Order verknüpft sind.

Bindungscode
Bisher haben Sie Steuerelemente entworfen, jedoch noch keinen Code für das Formular geschrieben. Die BindingSource-Steuerelemente für die Kunden, Mitarbeiter und Aufträge wurden bereits eingerichtet. Nun müssen Sie nur noch ihre Daten übergeben. Dafür rufen Sie ganz einfach zuerst die statische Methode „GetEntityList“ jeder Entität auf und eine List<T> der Entitäten ab. Die List<T> wird anschließend in eine BindingList<T> konvertiert und als Datenquelle für jedes entsprechende BindingSource-Steuerelement festgelegt.
Abbildung 6 zeigt, wie alle drei BindingSource-Steuerelemente auf ihre Listen gesetzt sind. Hier handelt es sich um den gesamten Code, der in der Benutzeroberfläche erforderlich ist, um dem Benutzer die Navigation und die Datenanzeige zu ermöglichen. Rufen Sie die SetupBindings-Methode im Konstruktor des Formulars auf, sodass die Daten beim ersten Laden des Formulars abgerufen, gebunden und angezeigt werden. Der Benutzer kann sich dann mithilfe der Navigationsschaltflächen auf der BindingNavigator-Symbolleiste oder durch Auswählen einer Zeile im DataGridView-Steuerelement durch die Datensätze bewegen. Es müssen jedoch noch die Ereignishandler geschrieben werden, die den Benutzern das Vornehmen der Änderungen ermöglichen.
private void SetupBindings()
{
    BindingList<Order> orderList = 
        new BindingList<Order>(Order.GetEntityList());
    orderBindingSource.DataSource = orderList;

    BindingList<Customer> customerList = 
        new BindingList<Customer>(Customer.GetEntityList());
    customerBindingSource.DataSource = customerList;

    BindingList<Employee> empList = 
        new BindingList<Employee>(Employee.GetEntityList());
    employeeBindingSource.DataSource = empList;
}


Speichern von Daten
Benutzern soll es in der Anwendung ermöglicht werden, den derzeit ausgewählten und angezeigten Auftrag zu löschen, indem sie auf der Symbolleiste auf die Schaltfläche „Löschen“ klicken. Zuerst wird die DeleteItem-Eigenschaft der Schaltfläche Löschen auf „keine“ gesetzt, um die Verwendung des benutzerdefinierten Codes zum Löschen zu erzwingen. Als Nächstes wird der für das Löschen verantwortliche Ereignishandler hinzugefügt. Dieser ruft eine private Methode mit dem Namen „Delete“ auf, wie Sie Abbildung 7 entnehmen können.
private void Delete()
{
    Order order = orderBindingSource.Current as Order;
    int orderID = order.OrderID;
    DialogResult dlg = MessageBox.Show(
        string.Format("Are you sure you want to delete Order # {0}?", 
        orderID.ToString()));
    if (dlg == System.Windows.Forms.DialogResult.OK)
    {
        Order.DeleteEntity(order);
        orderBindingSource.RemoveCurrent();
        MessageBox.Show(string.Format(
            "Order # {0} was deleted.", orderID.ToString()));
    }
}

Die Delete-Methode ruft den aktuellen Auftrag aus der aktuellen BindingSource-Eigenschaft ab und wandelt sie in den Entitätstyp „Order“ um. Anschließend wird der Benutzer gefragt, ob er den Auftrag wirklich löschen möchte. Wenn der Benutzer auf „OK“ klickt, wird die Order-Entität an die statische Methode „DeleteEntity“ im Klassenbibliotheksprojekt übergeben. Schließlich wird der Auftrag aus BindingSource entfernt, indem die RemoveCurrent-Methode des BindingSource-Steuerelements ausgeführt wird.
Die Symbolleiste von BindingNavigator besitzt zudem eine Schaltfläche zum Hinzufügen, mit der eine neue Zeile zu DataGridView hinzugefügt wird und die Steuerelemente auf der rechten Seite des Formulars gelöscht werden. Der Benutzer kann dann für den neuen Auftrag Werte eingeben und auswählen und auf der BindingNavigator-Symbolleiste auf die Schaltfläche „Speichern“ klicken. Ich habe dieser Schaltfläche zum Speichern einen Ereignishandler hinzugefügt, der die Auftragsentität an die statische Methode „SaveEntity“ der Order-Entität übergibt. Der Benutzer kann folglich den aktuellen Auftragsdatensatz bearbeiten und auf die gleiche Speichern-Schaltfläche klicken, um die geänderte Auftragsentität an die statische Methode „SaveEntity“ der Order-Entität zu übergeben. Die SaveEntity-Methode verarbeitet sowohl Einfügungen als auch Aktualisierungen durch Prüfen des Werts der OrderID-Eigenschaft der Order-Entität. Sobald die Entität eingefügt oder aktualisiert wurde, wird DataGridView neu geladen, indem die Auftragsliste erneut abgerufen wird und die Datenquellen von BindingSource erneut festgelegt werden, um aktuelle Daten zu erhalten.

Verwendung von Prädikaten zum Suchen einer Entität
Es ist wichtig, eine benutzerfreundliche Benutzeroberfläche zu erstellen. Die Benutzer möchten möglicherweise zu einem bestimmten Auftrag wechseln, wenn ihnen die Auftragsnummer bekannt ist. Zu diesem Zweck wurde zu BindingNavigator ein ToolStripTextBox-Steuerelement hinzugefügt, in das der Benutzer einen Teil oder die gesamte zu suchende OrderID eingeben kann. Anschließend wurden eine neue Symbolleistenschaltfläche und ein Ereignishandler hinzugefügt, der die Suche für den Auftragsdatensatz initiiert. Abbildung 1 zeigt diese Steuerelemente sowie den QuickInfo-Text, wo der Benutzer einen Auftrag durch Eingabe einer Auftragsnummer finden kann.
Mithilfe der Select-Methode oder der Find-Methode kann eine DataRow ganz einfach innerhalb einer DataTable gefunden werden. Sie können derartige Features selbst dann benutzen, wenn Sie benutzerdefinierte Entitäten verwenden. In diesem Fall müssen Sie eine Order-Entität in List<Order> finden, die mit dem Wert beginnt, den die Benutzer in die Suchsteuerung eingeben. List<T> stellt einige Methoden zur Verfügung, mit denen ein oder mehrere Elemente in der Liste gefunden werden können. Dazu zählen die Methoden „Find“, „FindAll“, „FindIndex“, „FindLast“ und „FindLastIndex“. Hier wird die Find-Methode verwendet, die Predicate<T> als einziges Argument annimmt.
Predicate<T> muss ein Predicate<Order> sein, da ich über List<Order> verfüge. Das Prädikat ist ein Delegat, das eine Order-Entität in List<Order> sucht, die mit den definierten Kriterien übereinstimmt (und folglich mit dem in das Suchfeld eingegebenen Suchwert beginnt). Vor dem Erstellen des Prädikats wurde zuerst eine Klasse erstellt, die die Suche unterstützen soll (siehe Abbildung 8). Die OrderFilter-Klasse nimmt den zu suchenden Auftrag in seinem Konstruktor an. Diese Klasse besitzt auch zwei Methoden zum Suchen einer bestimmten Entität. Beide Methoden geben einen Booleschen Wert zurück, der anzeigt, ob eine Entsprechung vorhanden ist. Diese Methoden stellen die Grundlage des Delegats dar, das an das Prädikat übergeben werden soll.
private class OrderFilter
{
    private int orderID = 0;
    
    public OrderFilter(int orderID)
    {
        this.orderID = orderID;
    }

    public bool MatchesOrderID(Order order)
    {
        return order.OrderID == orderID;
    }

    public bool BeginsWithOrderID(Order order)
    {
        return order.OrderID.ToString().StartsWith(orderID.ToString());
    }
}

Der Code, der den Auftrag sucht und BindingSource neu positioniert, wird in Abbildung 9 aufgeführt. Zuerst rufen Sie einen Verweis auf die Liste der Order-Entitäten aus BindingSource ab, um den Code übersichtlicher zu gestalten. Dann erstellen Sie eine Instanz der OrderFilter-Klasse und initialisieren sie mit der zu suchenden Auftrags-ID. Als Nächstes erstellen Sie das Prädikat und übergeben es an die BeginsWithOrderID-Methode der OrderFilter-Klasse. Schließlich führen Sie die Find-Methode von List<Order>aus und übergeben sie an das eben erstellte Prädikat. Dieses durchläuft wiederum die Liste der Order-Entitäten und übergibt alle an die OrderFilter.BeginsWithOrderID-Methode. Die erste Entität, die True zurückgibt, wird zurückgegeben und anschließend zur Neupositionierung von BindingSource zum Index verwendet.
private void toolBtnFindOrderNumber_Click(object sender, EventArgs e)
{
    List<Order> orderList = new List<Order>(
        orderBindingSource.DataSource as BindingList<Order>);
    OrderFilter orderFilter = new OrderFilter(
        Convert.ToInt32(toolTxtFindOrderNumber.Text));
    Predicate<Order> filterByOrderID = 
        new Predicate<Order>(orderFilter.BeginsWithOrderID);
    Order order = orderList.Find(filterByOrderID);
    if (order == null)
        MessageBox.Show("No matching Order found", 
                         "Not Found", MessageBoxButtons.OK);
    else
    {
        int index = orderBindingSource.IndexOf(order);
        orderBindingSource.Position = index;
    }
}



Zusammenfassung
In diesem Artikel wurde gezeigt, wie leistungsstark die Bindungssteuerelemente in .NET Windows Forms sind. Die Bindungssteuerelemente können mit vorhandenen Entitäten oder DataSets in Ihrer Architektur verwendet werden, um schnell ein gebundenes Formular zu erstellen. Wenn Sie für diese Steuerelemente zusätzliche Features benötigen, können Sie das Formular anpassen, um die gewünschte Funktionalität zu erhalten. Es spielt keine Rolle, ob Sie benutzerdefinierte Entitäten oder DataSets bevorzugen, weil beide Tools mithilfe der Datenbindung in einer .NET-Unternehmensanwendung implementiert werden können.

Senden Sie Fragen und Kommentare für John in englischer Sprache an mmdata@microsoft.com.


John Papa ist leitender .NET-Berater bei ASPSOFT (aspsoft.com) und ein Baseballfan, der in den meisten Sommernächten mit seiner Familie und seinem treuen Hund Kadi die Yankees anfeuert. John Papa, ein C#-MVP, hat mehrere Bücher über ADO, XML und SQL Server verfasst. Er hält häufig Vorträge auf Branchenkonferenzen, wie z. B. VSLive, und führt einen Blog unter codebetter.com/blogs/john.papa.

Page view tracker