m:n-Beziehung mit Listenfeld

Wenn Sie ein Abonnement des Magazins 'DATENBANKENTWICKLER' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

m:n-Beziehung mit Listenfeld

Wir haben in der Beispieldatenbank Bestellverwaltung bereits eine m:n-Beziehung über ein Fenster realisiert, das an eine Tabelle gebunden ist und ein DataGrid enthält, das die verknüpfte Elemente anzeigt. Dabei handelt es sich um die Abbildung von Bestellungen, Bestelldetails und Produkte, wo zusätzlich zur Verknüpfung noch weitere Daten wie der Einzelpreis für die Bestellposition gespeichert werden. Im vorliegenden Artikel werden wir uns ansehen, wie wir eine m:n-Beziehung ohne weitere Daten in der Verknüpfungstabelle verwalten können. Dazu wollen wir Kunden über eine Verknüpfungstabelle einer Tabelle mit Versendungen etwa zu Werbe- oder Informationszwecken verknüpfen.

Notwendige Tabellen/Erweiterungen

Die Datenbank Bestellverwaltung.db auf Basis von SQLite, die wir in den bisherigen Beispielen verwendet haben, müssen wir für diesen Artikel um zwei Tabellen erweitern.

Das erledigen wir schnell über das Verwaltungsprogramm SQLite Studio, mit dem wir die Datei Bestellverwaltung.db öffnen und als Erstes die Tabelle Versendungen anlegen – und zwar mit den Feldern wie in Bild 1. Speichern Sie die Tabelle nach dem Anlegen der Felder und des Tabellennamens mit einem Klick auf die Schaltfläche Commit structure changes.

Anlegen der neuen Tabelle Versendungen

Bild 1: Anlegen der neuen Tabelle Versendungen

Danach folgt die zweite Tabelle KundenVersendungen als Verknüpfungstabelle. Diese Tabelle enthält neben dem Primärschlüsselfeld ID noch die beiden Fremdschlüsselfelder KundeID und VersendungID, welche die beiden Tabellen Kunden und Versendungen referenzieren.

Wenn Sie die Fremdschlüsselfelder anlegen, aktivieren Sie im Dialog Spalte die Option Fremdschlüssel und klicken dann auf die Schaltfläche Konfigurieren. Dies zeigt den Dialog Bedingung editieren an, wo Sie als Fremde Tabelle die Tabelle Kunden und als Fremde Spalte die Spalte ID auswählen (siehe Bild 2). Hier aktivieren Sie außerdem die Option ON DELETE und wählen dafür die Aktion CASCADE aus.

Anlegen der neuen Tabelle KundenVersendungen

Bild 2: Anlegen der neuen Tabelle KundenVersendungen

Dies bewirkt, dass wenn Sie einen Datensatz der Tabelle Kunden löschen, für den in der Tabelle KundenVersendungen verknüpfte Datensätze vorliegen, diese auch gelöscht werden. Das Gleiche führen wir auch für das Fremdschlüsselfeld zum Verknüpfen mit der Tabelle Versendungen durch. Speichern Sie die Tabelle mit der Schaltfläche Commit structure changes.

Danach legen wir noch einen zusammengesetzten, eindeutigen Schlüssel für die beiden Fremdschlüsselfelder KundeID und VersendungID an, damit jede Versendung jedem Kunden nur einmal zugeordnet werden kann. Dazu klicken Sie oben über dem Tabellenentwurf auf den Reiter Indizes.

Klicken Sie dann auf die Schaltfläche Create index (ins). Dann tragen Sie im nun erscheinenden Dialog Index den Wert KundenVersendungenUniqueIndex als Name ein. Wählen Sie die beiden Felder KundeID und VersendungID aus und aktivieren Sie die Option Einzigartiger Index (okay, die Übersetzung ist zumindest gut gemeint von den Entwicklern des Tools). Dies soll dann wie in Bild 3 aussehen, bevor Sie auf OK klicken und anschließend das Anlegen des Indizes bestätigen.

Anlegen eines zusammengesetzten, eindeutigen Schlüssels über die beiden Felder KundeID und VersendungID

Bild 3: Anlegen eines zusammengesetzten, eindeutigen Schlüssels über die beiden Felder KundeID und VersendungID

Änderungen im Entity Data Model

Nachdem Sie die beiden neuen Tabellen angelegt haben, benötigen wir in der Anwendung die entsprechenden Entitäten für die beiden Tabellen. Dazu öffnen Sie die Anwendung Bestellverwaltung und klicken im Projekt-Explorer doppelt auf das Objekt Bestellverwaltung.edmx.

Klicken Sie mit der rechten Maustaste in das Diagramm und wählen Sie aus dem Kontextmenü den Eintrag Modell aus der Datenbank aktualisieren... aus. Gegebenenfalls müssen Sie nun die Verbindung für das Entity Data Model festlegen. Danach erscheint dann der Dialog, der auch beim Erstellen eines Entity Data Models angezeigt wird. Hier wählen Sie die beiden einzigen angezeigten Tabellen KundenVersendungen und Versendungen aus und klicken auf Fertig stellen.

Danach sollten die neuen Tabellen wie in Bild 4 im Entity Data Model angezeigt werden, sodass wir nun darauf zugreifen können. Genau wie bei den übrigen Entitäten haben wir auch bei diesen beiden Entitäten die Benennung in den Singular geändert, also von KundenVersendungen auf KundeVersendung und von Versendungen auf Versendung.

Die beiden neuen Entitäten im Entity Data Model, hier bereits in der Singular-Benennung

Bild 4: Die beiden neuen Entitäten im Entity Data Model, hier bereits in der Singular-Benennung

Wenn Sie nun noch den neuen Zustand etwa mit der Tastenkombination Strg + S speichern, werden auch die dahinter liegenden Objekte umbenannt.

Fenster zum Zuordnen von Kunden und Versendungen

Das Fenster, in dem wir einer Versendung die entsprechenden Kunden zuordnen wollen, soll im oberen Bereich ein Kombinationsfeld enthalten, welches die Versendungen abbildet. Im unteren Bereich wollen wir zwei Listenfelder darstellen. Das linke enthält alle Kunden, die der Versendung bereits zugeordnet wurden und das rechte alls übrigen Kunden. Dazwischen platzieren wir vier Schaltflächen für die folgenden Funktionen:

  • <: Den aktuell markierten Kunden aus dem rechten Listenfeld zur Liste der Empfänger hinzufügen.
  • >: Den aktuell markierten Kunden aus dem linken Listenfeld aus der Liste der Empfänger entfernen.
  • <<: Alle Kunden aus dem rechten Listenfeld zur Liste der Empfänger hinzufügen.
  • >>: Alle Kunden aus dem linken Listenfeld aus der Liste der Empfänger entfernen.

Fenster anlegen

Das neue Fenster wollen wie diesmal einmal nicht in die bestehende Struktur einbinden, sondern über den Ribbon-Eintrag Versendungen als eigenes Fenster öffnen. Das Fenster legen Sie im Projekt unter dem Namen Versendungen.xaml an. Dem Fenster MainWindow.xaml fügen wir eine neue Ribbon-Schaltfläche wie in Bild 5 hinzu. Der Code dafür sieht wie folgt aus:

Neue Ribbon-Schaltfläche zum Anzeigen des Fensters mit den Zuweisungen der Versendungen

Bild 5: Neue Ribbon-Schaltfläche zum Anzeigen des Fensters mit den Zuweisungen der Versendungen

<RibbonGroup Header="Versendungen">
     <RibbonButton Name="btnVersendungen" Label="Versendungen" Click="btnVersendungen_Click" 
         LargeImageSource="imagesmail2.png"></RibbonButton>
</RibbonGroup>

Für die Schaltfläche hinterlegen wir die folgende Ereignismethode:

private void btnVersendungen_Click(object sender, 
         RoutedEventArgs e) {
     Versendungen wnd = new Versendungen();
     wnd.ShowDialog();
}

Damit schauen wir uns nun den Entwurf des Fensters Versendungen.xaml an (siehe Bild 6). Das Fenster enthält drei Spalten und drei Zeilen. Die erste Zeile enthält lediglich das Kombinationsfeld cboVersendungen zur Auswahl der gewünschten Versendung. Der Inhalt befindet sich in einem horizontal ausgerichteten StackPanel-Element, dass sich über alle drei Spalten erstreckt (Grid.ColumnSpan="3").

Entwurf des Fensters Versendungen.xaml

Bild 6: Entwurf des Fensters Versendungen.xaml

Die zweite Zeile enthält in der ersten Spalte das erste Listenfeld, in der zweiten Spalte ein vertikales StackPanel-Element mit den vier Schaltflächen und die dritte Spalte das zweite Listenfeld. Die dritte Spalte schließlich enthält eine OK-Schaltfläche zum Schließen des Fensters. Der .xaml-Code des Fensters sieht in gekürzter Form wie folgt aus:

<Window x:Class="Bestellverwaltung.Versendungen" ... Title="Versendungen" Height="300" Width="400">
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"></RowDefinition>
             <RowDefinition Height="*"></RowDefinition>
             <RowDefinition Height="Auto"></RowDefinition>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition></ColumnDefinition>
             <ColumnDefinition Width="Auto"></ColumnDefinition>
             <ColumnDefinition></ColumnDefinition>
         </Grid.ColumnDefinitions>
         <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
             <Label Margin="5">Versendung:</Label>
             <ComboBox x:Name="cboVersendungen" Margin="5"></ComboBox>
         </StackPanel>
         <ListBox Grid.Column="0" Grid.Row="1" Margin="5"></ListBox>
         <StackPanel Orientation="Vertical" Grid.Column="1" Grid.Row="1">
             <Button x:Name="btnEinenHinzufuegen" Margin="5" Content="<"></Button>
             <Button x:Name="btnAlleHinzufuegen" Margin="5" Content="<<"></Button>
             <Button x:Name="btnEinenEntfernen" Margin="5" Content=">"></Button>
             <Button x:Name="btnAlleEntfernen" Margin="5" Content=">>"></Button>
         </StackPanel>
         <ListBox Grid.Column="2" Grid.Row="1" Margin="5"></ListBox>
         <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
             <Button x:Name="btnOK" Margin="5" Click="btnOK_Click">OK</Button>
         </StackPanel>
     </Grid>
</Window>

Interessant ist hier, wie wir die Breite und die Höhe der Spalten und Zeilen eingestellt haben. Damit die mittlere Spalte genau die für die vier Schaltflächen in dem Stackpanel benötigte Spaltenbreite annimmt, haben wir die Eigenschaft Width auf den Wert Auto eingestellt. Die linke und die rechte Spalte sollen sich den übrigen Platz gleichmäßig aufteilen, daher erhält die Eigenschaft Width hier den Wert *.

Die obere Zeile und die untere Zeile sollen wie die mittlere Spalte nur den benötigten Platz einnehmen, also stellen wir die Eigenschaft Height für beide auf den Wert Auto ein. Die mittlere Zeile soll den Rest der Höhe beanspruchen, war wir durch den Wert * für die Eigenschaft Height erreichen.

Die Definition enthält bislang nur die reinen Steuer­elemente ohne jegliche Bindung.

Kombinationsfeld mit den Versendungen füllen

Um das Kombinationsfeld mit den Datensätzen der Tabelle Versendungen in alphabetischer Reihenfolge zu füllen, müssen zunächst im Code behind-Modul eine entsprechende Auflistung erzeugen und aus dem Entity Data Model füllen.

Dazu erstellen wir zunächst einen Datenbank-Kontext im allgemeinen Teil der Klasse, der somit direkt beim Erstellen angelegt wird:

BestellverwaltungEntities dbContext = new BestellverwaltungEntities();

Für den Inhalt des Kombinationsfeldes legen wir eine ObservableCollection an, die wir als private Variable namens versendungen sowie als öffentliche Variable namens AlleVersendungen definieren (AlleVersendungen, weil wir bereits das Fenster Versendungen genannt haben):

private ObservableCollection<Versendung> versendungen;
public ObservableCollection<Versendung> AlleVersendungen{
     get { return versendungen; }
     set { versendungen = value; }
}

In der Konstruktor-Methode des Fensters füllen wir dann zunächst die ObservableCollection, die als Datenquelle für das Kombinationsfeld dienen soll und weisen dem Fenster die Code behind-Klasse als DataContext zu:

public Versendungen() {
     InitializeComponent();
     versendungen = new ObservableCollection<Versendung>(dbContext.Versendungen);
     DataContext = this;
}

Nun passen wir in Versendungen.xaml die Definition des ComboBox-Elements an, damit es die Daten der ObservableCollection anzeigt:

<ComboBox x:Name="cboVersendungen" ... IsEditable="True" 
     Text="{Binding NewEntry, UpdateSourceTrigger=LostFocus}" 
     ItemsSource="{Binding AlleVersendungen}" 
     DisplayMemberPath="Bezeichnung" 
     SelectedValuePath="ID"
     VerticalAlignment="Center"></ComboBox>

Hier haben wir gleich die Vorbereitungen getroffen, dass der Benutzer neue Versendungen direkt in das Kombinationsfeld eingeben kann. Wie das im Detail funktioniert, erläutern wir im Artikel Neuer Eintrag in ComboBox. Hier nur die Kurzfassung: IsEditable="True" sorgt dafür, dass der Benutzer Text in das Kombinationsfeld eintippen kann. Das Binding für das Attribut Text sorgt dafür, dass eine Eigenschaftsmethode namens NewEntry neue Werte entgegennimmt und UpdateSourceTrigger="LostFocus" sorgt dafür, dass die Methode NewEntry beim Fokusverlust ausgelöst wird. Die Methode NewEntry sieht so aus:

public string NewEntry {
     set {
         if (cboVersendungen.SelectedIndex == -1) {
             if (!string.IsNullOrEmpty(value)) {
                 MessageBoxResult result = MessageBox.Show("Eintrag '" + value + "' zu den Versendungen hinzufügen?", 
                     "Neuer Eintrag", MessageBoxButton.YesNo);
                 if (result == MessageBoxResult.Yes) {
                     Versendung newVersendung = new Versendung();
                     newVersendung.Bezeichnung = value;
                     versendungen.Add(newVersendung);
                     dbContext.Versendungen.Add(newVersendung);
                     dbContext.SaveChanges();
                 }
             }
         }
     }
}

Sie prüft, ob der Inhalt des eingegebenen Textes, der wie üblich über den Parameter value geliefert wird, nicht leer ist und fragt in diese Fall den Benutzer, ob er den Wert als neuen Eintrag hinzufügen möchte (siehe Bild 7). Falls ja, legt die Methode ein neues Objekt des Typs Versendung an und weist value als Bezeichnung hinzu. Dann fügt sie das neue Objekt zur ObservableCollection und zur entsprechenden Liste des Entity Data Models hinzu und speichert die Änderung in der Datenbank.

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.