EF: Daten abfragen mit VB und LINQ

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.

EF: Daten abfragen mit VB und LINQ

Unter Access waren Sie es gewöhnt, auf einfache Weise Abfragen mit der Abfrage-Entwurfsansicht zu erstellen. Ein paar Tabellen hinzufügen, die Felder auswählen, Kriterien, Sortierungen und Gruppierungen hinzufügen – fertig war die Abfrage. Gegebenenfalls haben Sie SQL-Kenntnisse und konnten SQL-Anweisungen für den Einsatz in VBA-Anweisungen von Hand schreiben. Unter VB und Entity Framework sieht das anders aus, weil wir ja nicht mehr auf Tabellen zugreifen, sondern auf Objekte. Und für die gibt es eine andere Abfragesprache, die sich direkt in den VB-Code integrieren lässt. Dieser Artikel stellt die Abfragetechnik LINQ für Visual Basic vor.

Voraussetzungen

Um die Beispiele dieses Artikels nachstellen zu können, benötigen das Entity Data Model aus dem Beispielprojekt aus dem Download zu diesem Artikel. Für die Beispielabfragen fügen wir dem Fenster MainWindow.xaml unserer Beispielanwendung ein DataGrid-Steuerelement hinzu. Der Code zum Füllen des DataGrid-Elements mit allen Eigenschaften aller Kunden-Elemente wie in Bild 1 sieht so aus:

DataGrid mit allen Kundendaten

Bild 1: DataGrid mit allen Kundendaten

Imports System.Collections.ObjectModel
Class MainWindow
     Private dbContext As BestellverwaltungContext
     Private _Kunden As ObservableCollection(Of Kunde)
     Public Property Kunden As ObservableCollection(Of Kunde)
         Get
             Return _Kunden
         End Get
         Set(value As ObservableCollection(Of Kunde))
             _Kunden = value
         End Set
     End Property
     Public Sub New()
         InitializeComponent()
         dbContext = New BestellverwaltungContext
         Kunden = New ObservableCollection(Of Kunde)(dbContext.Kunden)
         DataContext = Me
     End Sub
End Class

Den XAML-Code haben wir so einfach wie möglich gehalten:

<Window x:Class="MainWindow" ... Title="MainWindow" Height="450" Width="800">
     <Grid>
         <DataGrid ItemsSource="{Binding Kunden}"></DataGrid>
     </Grid>
</Window>

Die Anweisung der Konstruktor-Methode New(), die wir als Ausgangspunkt für die folgenden Beispiele nutzen, ist diese:

Kunden = New ObservableCollection(Of Kunde)(dbContext.Kunden)

Hier weisen wir der ObservableCollection mit Elementen des Typs Kunde einfach alle Kunden des Datenbankkontextes zu, also dbContext.Kunden. Statt dbContext.Kunden können wir auch die nachfolgend vorgestellten Abfrageausdrücke verwenden.

Einfache Auswahlabfrage

Die einfachste Syntax bei einer LINQ-Abfrage sieht wie folgt aus – hier noch in Zusammenhang mit unserer Anweisung:

Kunden = New ObservableCollection(Of Kunde)(From Kunde In dbContext.Kunden)

Der Einfachheit halber schauen wir uns nun noch den Teil an, den wir in eine ObservableCollection umwandeln, hier also diesen Teil:

From Kunde In dbContext.Kunden

Dies liefert alle Elemente der Kunden-Auflistung. dbContext.Kunden ist dabei die Quelle der Daten. Kunde ist eine Variable, auf die wir mit weiteren Schlüsselwörtern der Abfragesprache LINQ zugreifen können. Diese Abfrage liefert das gleiche Ergebnis wie dbKontext.Kunden.

Eine ausführlichere Schreibweise unter Einbeziehung des Select-Schlüsselworts sieht wie folgt aus und liefert wiederum das gleiche Ergebnis:

From Kunde In dbContext.Kunden Select Kunde

Daten filtern mit Where

Wenn Ihnen die bisherigen Schlüsselwörter schon von SQL bekannt vorkamen, gibt es gute Nachrichten – das Where-Schlüsselwort dürften Sie auch kennen. Dieses hängen Sie hinter die From-Anweisung an und vor der Select-Anweisung.

Das folgende Beispiel selektiert beispielsweise nur den Kunden, dessen Feld ID den Wert 1 aufweist (Ergebnis siehe Bild 2):

DataGrid mit dem Kunden mit dem Wert 1 im Feld ID

Bild 2: DataGrid mit dem Kunden mit dem Wert 1 im Feld ID

From Kunde In dbContext.Kunden Where Kunde.ID = 1 Select Kunde

Ersatz für den SQL-LIKE-Operator: Contains, StartsWith und EndsWith

Unter SQL haben Sie Vergleiche mit Platzhaltern wie dem Sternchen mit dem LIKE-Operator realisiert, also etwa mit SELECT * FROM tblKunden WHERE PLZ LIKE "1*", um alle Kunden mit einer PLZ, die mit 1 beginnt, zu ermitteln.

Unter LINQ gibt es dazu die drei Operatoren Contains, StartsWith und EndsWith:

  • Contains: Entspricht der Suche nach einem Ausdruck, der irgendwo im Feldinhalt vorkommen darf, also gleichbedeutend mit LIKE "**".
  • StartsWith: Sucht nach Datensätzen, deren zu durchsuchendes Feld mit dem angegebenen Ausdruck beginnt. Unter SQL wäre das LIKE "*".
  • EndsWith: Sucht nach Datensätzen, deren zu durchsuchendes Feld auf den angegebenen Ausdruck endet. Unter SQL entspricht das LIKE "*".

Die Operatoren hängen wir an das zu durchsuchende Feld an und geben den Suchbegriff in Klammern an. Alle Kunden, deren PLZ mit 1 beginnt, ermitteln wir also mit dem folgenden Ausdruck:

From Kunde In dbContext.Kunden Where Kunde.PLZ.StartsWith("1") Select Kunde

Kriterien verknüpfen

Die Suchkriterien verknüpfen Sie einfach wie von SQL gewohnt mit den Operatoren Or und And. Kunden, deren Vorname und Nachname mit A beginnt, finden Sie beispielsweise so:

From Kunde In dbContext.Kunden Where Kunde.Vorname.StartsWith("A") Or Kunde.Nachname.StartsWith("A") Select Kunde

Daten sortieren mit Order By

Natürlich können Sie die Daten auch in beliebiger Sortierung in die ObservableCollection füllen. Dazu verwenden wir den folgenden Abfrageausdruck, in dem wir das Order By-Schlüsselwort gefolgt von dem zu sortierenden Feld angeben:

From Kunde In dbContext.Kunden Order By Kunde.Nachname Select Kunde

Wenn Sie nach mehreren Feldern sortieren wollen, geben Sie diese durch ein Komma voneinander getrennt hinter dem Order By-Schlüsselwort an:

From Kunde In dbContext.Kunden Order By Kunde.Nachname, Kunde.Vorname Select Kunde

Hier haben wir noch nicht explizit die Sortierreihenfolge angegeben. In diesem Fall sortiert die Abfrage aufsteigend nach den angegebenen Feldern. Sie können die Sortierreihenfolge auch explizit angeben, indem Sie eines der Schlüsselwörter Ascending (Aufsteigend) oder Descending (Absteigend) hinter dem Sortierfeld angeben:

From Kunde In dbContext.Kunden Order By Kunde.Nachname Ascending, Kunde.Vorname Descending Select Kunde

Reihenfolge der Elemente

Im Gegensatz zu SQL, wo die Reihenfolge strikt festgelegt ist (zum Beispiel SELECT ... FROM ... WHERE ... ORDER BY), können Sie in LINQ etwa die Reihenfolge der Where- und der Order By-Klausel vertauschen. Wir empfehlen jedoch, die Reihenfolge wie unter SQL zu gestalten, um die Lesbarkeit zu erhöhen. Die folgende Abfrage, bei der wir die Order By-Klausel vor der Where-Klausel platziert haben, funktioniert jedoch genauso wie die mit der umgekehrten Reihenfolge:

From Kunde In dbContext.Kunden Order By Kunde.Nachname Ascending Where Kunde.Nachname.StartsWith("A") Select Kunde

Die »Laufvariable«

Die Bezeichnung zwischen From und In, in unserem Fall also etwa Kunde, können Sie beliebig wählen. Wenn Sie den Abfragecode kürzer darstellen wollen, können Sie auch einfach k verwenden:

From k In dbContext.Kunden Where k.Nachname.StartsWith("A") Order By k.Nachname Ascending Select k

Zu selektierende Daten festlegen mit Select

Die Select-Anweisung haben wir in den obigen Beispielen schon ausgiebig genutzt. Allerdings haben wir mit ... Select Kunde immer einfach die kompletten Kunde-Objekte selektiert. Das hätten wir auch erreicht, wenn wir die Select-Klausel weggelassen hätten. Select bietet allerdings eine Menge mehr als nur die Möglichkeit, genau die Objekte zurückzuliefern, welche die zu untersuchende Auflistung bietet, in diesem Fall die Liste der Kunde-Objekte. In allen Fällen, in denen Sie nicht einfach die Objekte zurückliefern, die in der zu untersuchenden Auflistung enthalten sind, handelt es sich um eine sogenannte Projektion. Schauen wir uns an, wie das aussehen kann! Wenn wir beispielsweise nur die ID, den Vornamen und den Nachnamen des Kunden erhalten wollen, formulieren wir den Ausdruck wie folgt:

From k In dbContext.Kunden Select k.ID, k.Vorname, k.Nachname

Allerdings liefert dies dann kein Objekt vom Typ Kunde mehr zurück, was sich in einer entsprechenden Fehlermeldung bemerkbar macht (siehe Bild 3). Also spielen wir erst einmal außerhalb des DataGrids mit dieser Möglichkeit:

Fehlermeldung, wenn keine Objekte des Typs Kunde zurückgeliefert werden

Bild 3: Fehlermeldung, wenn keine Objekte des Typs Kunde zurückgeliefert werden

Dim KundenMitName = From k In dbContext.Kunden Select k.ID, k.Vorname, k.Nachname
For Each k In KundenMitName
     Debug.Print(k.ID.ToString() + " " + k.Vorname + " " + k.Nachname)
Next

Wir weisen also das Ergebnis, das die Eigenschaften ID, Vorname und Nachname der Elemente aus dbContext.Kunden enthält, der Variablen KundenMitName zu. KundenMitName wir dann zu einer Variablen des Typs DbQuery. Auf die enthaltenen Eigenschaften können wir wie auf die Eigenschaften einer Klasse zugreifen. Im Beispiel oben durchlaufen wir diese Elemente in einer For Each-Schleife, wobei wir das aktuelle Element der Variablen k zuweisen. Diese hält dann die Eigenschaften ID, Vorname und Nachname bereit. Diese geben wir innerhalb der Schleife mit der Debug.Print-Anweisung im Output-Fenster aus.

DbQuery-Ergebnis in DataGrid anzeigen

Wie aber zeigen wir dieses Ergebnis im DataGrid an? Dieses ist ja an die Eigenschaft Kunden der Code behind-Klasse gebunden. Wenn wir dies nicht ändern wollen, müssen wir die Informationen ID, Vorname und Nachname anderweitig in Elemente des Typs Kunde schreiben und diese dann der Auflistung zuweisen. Aber gelingt uns das? Der theoretische Ansatz sieht wie folgt aus:

Dim KundenMitName = From k In dbContext.Kunden Select New Kunde With {.ID = k.ID, .Vorname = k.Vorname, .Nachname = k.Nachname}
Kunden = New ObservableCollection(Of Kunde)(KundenMitName)

Hier weisen wir einer Variablen namens KundenMitName das Ergebnis der Abfrage in Form von Objekten mit den Eigenschaften ID, Vorname und Nachname zu. Danach wollen wir das Ergebnis der öffentlichen ObservableCollection-Variablen Kunden zuweisen. Das liefert allerdings den Fehler The entity or complex type 'EFAbfragnVB.Kunde' cannot be constructed in a LINQ to Entities query. Der Grund für den Fehler ist, dass die Kunde-Entitäten nur teilweise mit den Daten aus der Datenbank gefüllt werden, was in einem nicht aktualisierbaren Zustand resultiert.

Also verwenden wir doch eine neue Klasse, um das neue Ergebnis zu speichern. Diese Klasse sieht wie folgt aus:

Public Class KundeMitName
     Public Property ID As System.Int32
     <StringLength(255)>
     <Required>
     Public Property Vorname As System.String
     <StringLength(255)>
     <Required>
     Public Property Nachname As System.String
End Class

Den Code der Code behind-Klasse von KundenMitName.xaml gestalten wir wie folgt, wobei wir die neue private Variable und die öffentliche Eigenschaft _KundenMitName und KundenMitName nennen:

Public Class KundenMitName
     Private dbContext As BestellverwaltungContext
     Private _KundenMitName As List(Of KundeMitName)
     Public Property KundenMitName As List(Of KundeMitName)
         Get
             Return _KundenMitName
         End Get
         Set(value As List(Of KundeMitName))
             _KundenMitName = value
         End Set
     End Property

In der Konstruktor-Methode füllen wir die Variable KundenMitNamen dann über die folgende Anweisung, in der wir hinter der Select-Klausel jeweils ein neues KundeMitName-Objekt erzeugen und seinen drei Eigenschaften ID, Vorname und Nachname die entsprechenden Werte des jeweiligen Datensatzes zuweisen:

     Public Sub New()
         InitializeComponent()

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.