3 VB.NET Linq
3.1 LINQ Übersicht
Ab .NET 3.5 wurden die Compiler erweitert um
Sprachmittel zum Umgang mit Mengen. Diese wurden LINQ (Language
integrated query)
getauft.
LINQ basiert auf weitere neue Sprachmittel in .NET
3.5 Compiler wie Typinferenz,
anonyme Typen, Lambda
Ausdrücke usw.. Es gibt
zwei Sprachstile:
Beispiel für die
Abfragesyntax:
Dim monate() AS String = { "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "dez" }
' Linq- Abfrage in Abfragesyntax auf einer Objektmenge
Dim res = From s In monate Where s(0) = "m" Select s
// Ausgabe des Ergebnisses
For Each m as String in res
Debug.WriteLine(m)
Next
Beispiel für Erweiterungsmethodensyntax
Dim res2 = monate.Where(Function(s) s(0) = "m").Select(Function(s) New With {.Name= s, .NUp= s.ToUpper()})
For Each m in res2
Debug.WriteLine(m.Name + ", " + m.NUp)
Next
3.1.1 Linq- Bibliotheken
Linq wird für
verschiedene Mengen implementiert:
Bibliotheken
|
Anwendung
|
Linq To Object
|
Filtern von Datensätzen in Arrays und
Collections
|
Linq To DataSet
|
Komplexe Abfragen auf Datatables in Datasets
unter Berücksichtigung von Beziehungen
|
Linq To Xml
|
Zugriff auf Daten in XML Dokumenten und
Fragmenten mittels Linq- Ausdrücken
|
Linq To SQL
|
Formulieren komplexer DML Befehle auf SQL-
Servern, die vom Compiler zur Entwurfszeit geprüft werden.
Abbilden von Relationen auf Objekte (ORM).
|
3.2 Linq To Object
Mittels Linq To Object können in Arrays und
Collections organisierte Menge in einem SQL- ähnlichen
deklarativen Stil gefiltert werden. Im Folgenden wird für ein
Array mit Primzahlen die Teilmenge aller Primzahlen > 10 bestimmt:
' Abfragen auf Arrays
Dim primz() As Integer = { 2, 3, 5, 7, 11, 13, 19, 23 }
' Liste einschänken auf Wert > 10
Dim größer10() As Integer = primz.Where(Function(p) p > 10).ToArray()
For Each p as Integer in größer10
Trace.WriteLine(p);
Next
Linq offenbart sich hier durch den Where Operator.
Das Ergebnis des Where Operators ist vom Typ IEnumerable<int>.
In .NET 3.5 ist die IEnumerable<T> Schnittstelle um viele
Methoden erweitert worden, die die Kernfunktionalität von Linq
definieren. Die Ienumerable<T>.ToArray() Methode konvertiert
das Ergebnis wieder in ein Array vom Typ T (im Beispiel oben in ein
int[]).
3.2.1 Linq- Methoden in
IEnumerable<T>
In .NET 3.5 wurde die
System.Collections.Generics.IEnumerable<T> Schnittstelle mit
neuen Methoden ausgestattet, die die Kernfunktionalität von Linq
bereitstellen. Im Folgenden ein Ausschnitt:
3.2.2 Projektion
Bei Abfragen wird aus Daten nicht nur eine
Teilmenge ausgewählt. Häufig wird aus werden die Elemente
der Teilmenge in eine neue Form umgewandelt. In der Mathematik wird
dies als Projektion bezeichnet, wobei zu jedem Element ein Element in
der Zielmenge ausgewählt und dem Ergebnis hinzugefügt wird.
In Linq wird dies analog zu SQL mit dem Select-
Operator realisiert.
Dim kalender as New Dictionary(Of DateTime, string)
kalender(New DateTime(2009, 12, 24)) = "Weihnachten"
kalender(New DateTime(2008, 12, 24)) = "Weihnachten"
kalender(New DateTime(2009, 12, 31)) = "Silvester"
kalender(New DateTime(2009, 12, 31)) = "Silvester"
Dim alle2009 as Dictionary(Of DateTime, string) = kalender
.Where(Function(pair) pair.Key.Year = 2009)
.ToDictionary(Function(pair) pair.Key, Function(pair) pair.Value)
' Projektion mittels Select
Dim listeDerTage() as Integer = alle2009.Select(Function(pair) pair.Key.Day).ToArray();
' QueryExpression Syntax
Dim listeDerTage2() as Integer = (From pair In kalender
Where pair.Key.Year = 2009
Select pair.Key.Day).ToArray()
3.2.3 Aggregationen
Mittels Aggregationen können
Detailinformation verdichtet werden. Typische statistische
Basisfunktionen wie Minimum, Maximum, Durchschnitt und Summe werden
von Linq bereitgestellt.
Dim allDcp() As Descriptor = {
New Descriptor() With {.FileTyp = "gif", .SizeInMB = 2000, .Art = Descriptor.EnumArt.grafik},
New Descriptor() With {.FileTyp = "jpg", .SizeInMB = 5677, .Art = Descriptor.EnumArt.foto},
New Descriptor() With {.FileTyp = "tif", .SizeInMB = 3978, .Art = Descriptor.EnumArt.foto},
New Descriptor() With {.FileTyp = "bmp", .SizeInMB = 4322, .Art = Descriptor.EnumArt.grafik},
New Descriptor() With {.FileTyp = "mp3", .SizeInMB = 25740, .Art = Descriptor.EnumArt.video},
New Descriptor() With {.FileTyp = "avi", .SizeInMB = 12311, .Art = Descriptor.EnumArt.video}}
' Alle von der Art grafik
' Erweiterungsmethodensyntax
Dim resGrafik = allDcp.Where(Function(dsc As Descriptor) dsc.Art = Descriptor.EnumArt.grafik)
' Abfragesyntax
Dim resGrafik2 = From e In allDcp Where e.Art = Descriptor.EnumArt.grafik
' Speicherplatzbedarf aller Dateien
Dim mem As Long = allDcp.Sum(Function(dsc) dsc.SizeInMB)
mem = allDcp.Min(Function(dsc) dsc.SizeInMB)
mem = allDcp.Max(Function(dsc) dsc.SizeInMB)
mem = allDcp.Average (Function(dsc) dsc.SizeInMB)
3.2.4 Join
Sind Datenmengen normalisert, dann werden die
Detaildaten zu einem Entity in separaten Mengen geführt. Um
Master- und Detaildaten wieder zu einem vollständigen Datensatz
zusammenzuführen, bietet Linq den Join. Operator an:
/*
Join:
Array aller Artikel und Array aller Lieferanten wird mittels eines Objektes der Klasse
BoLieferantenDs aufgebaut, das aus der Beispieldatenbank ArtikelDb Datensätze in
DataSets kopiert
*/
ArtikelDbLib.BoLieferantenDs.ConnectionString = Properties.Settings.Default.ArtikelDBConnString;
ArtikelDbLib.BoLieferantenDs boLF = new ArtikelDbLib.BoLieferantenDs();
// ArtikelDb in Dataset kopieren
ArtikelDbLib.DsArtikelDb ds = boLF.selectAlleLieferantenDataset();
// Array mit allen Artikeldatensätzen anlegen
ArtikelDbLib.DsArtikelDb.ArtikelRow[] alleArt = ds.Artikel.ToArray();
// Array mit allen Lieferantendatensätzen anlegen
ArtikelDbLib.DsArtikelDb.LieferantenRow[] alleLF = ds.Lieferanten.ToArray();
// Joinen von Daten, die in Array gehalten werden
var res = alleLF
.Join(alleArt, // "Detail"- Menge, mit der Join erfolgt
lf => lf.lfnr, // Schlüssel aus Master
art => art.lfnr, // Fremdschüssel im Detail
(lf, art) => new { // Projekttionsvorschrift, die Elemente des
// Ergebnisses liefert
lfname = lf.name,
artNr = art.artnr,
artPreis = art.preis
}
);
foreach (var resJoin in res)
{
Trace.WriteLine(resJoin.ToString());
}
3.2.5 Gruppierungen
Elemente einer Menge besitzen im Allgemeinen
Eigenschaften. Bezüglich dieser Eigenschaften können sie
klassifiziert werden, man spricht in Zusammenhang mit SQL auch von
Gruppierung. Linq bietet mit dem Goup Operator eine analoge Funktion
wie Sql.
/*
Gruppierung:
Array aller Artikel und Array aller Lieferanten wird mittels eines Objektes der Klasse
BoLieferantenDs aufgebaut, das aus der Beispieldatenbank ArtikelDb Datensätze in
DataSets kopiert
*/
ArtikelDbLib.BoLieferantenDs.ConnectionString = Properties.Settings.Default.ArtikelDBConnString;
ArtikelDbLib.BoLieferantenDs boLF = new ArtikelDbLib.BoLieferantenDs();
// ArtikelDb in Dataset kopieren
ArtikelDbLib.DsArtikelDb ds = boLF.selectAlleLieferantenDataset();
// Array mit allen Artikeldatensätzen anlegen
ArtikelDbLib.DsArtikelDb.ArtikelRow[] alleArt = ds.Artikel.ToArray();
// Array mit allen Lieferantendatensätzen anlegen
ArtikelDbLib.DsArtikelDb.LieferantenRow[] alleLF = ds.Lieferanten.ToArray();
// Gruppierung
var resGroups = alleLF.Join(
alleArt,
lf => lf.lfnr,
art => art.lfnr,
(lf, art) => new {
lfNr = lf.lfnr,
lfname = lf.name,
artNr = art.artnr,
artPreis = art.preis })
.GroupBy(dsJoined => dsJoined.lfNr);
foreach (var group in resGroups)
{
Trace.WriteLine("Lfnr: " + group.Key);
foreach (var dsJoined in group)
{
Trace.WriteLine("\t" + dsJoined.artNr);
}
}
Die Ergebnisse eine Gruppierung sind wiederum Mengen. Diese sind in
Linq Objekte, die die Schnittstelle System.Linq.IGrouping<TKey,
Telement> implementieren.
Mit
dem SelectMany Operator kann
man die Elemente einer Gruppe auf die Elemente einer denormalisierten
Darstellung der Menge aller Gruppen einer Abfrage projezieren.
// SelectMany - Experiment
var resGroupsDenormalisiert = alleLF.Join(
alleArt,
lf => lf.lfnr,
art => art.lfnr,
(lf, art) => new {
lfNr = lf.lfnr,
lfname = lf.name,
artNr = art.artnr,
artPreis = art.preis })
.GroupBy(dsJoined => dsJoined.lfNr)
.SelectMany(dsJoined => dsJoined);
foreach (var group in resGroupsDenormalisiert)
{
Trace.WriteLine("Lfnr: " + group.lfNr + ", artNr: " + group.artNr);
}
3.3 Linq To DataSet
Mit Linq To DataSet werden die mächtigen
Möglichkeiten zum Filtern von Mengen mittels Linq auch für
DataSets und den mit ihnen verwalteten DataTables implementiert.
Eine Besonderheit der Implementierung ist die
Konvertierung in eine DataView (AsDataView), mittels der die
Ergebnisse von Linq- Operationen direkt den Steuerelementen in
WindowsForm zugewiesen werden können.
// ConnString setzen
ArtikelDbLib.BoLieferantenDs.ConnectionString = Properties.Settings.Default.ArtikelDbConnectionString;
ArtikelDbLib.BoLieferantenDs boLF = new ArtikelDbLib.BoLieferantenDs();
boLF.FillAlleLieferantenDataset(myDsArtikelDb);
// LinqTo Dataset alle Lieferanten Filtern, die ...
dataGridView2.DataSource = myDsArtikelDb.Lieferanten
.Where(lfRow => lfRow.GetArtikelRows().Count() > 1)
.AsDataView();
3.4 Linq To Xml
[Noch nicht implementiert :-)]
3.5 Linq To SQL
Linq To Sql implementiert Linq für SQL-
Server. Aktuell wird nur der Microsoft SQL- Server unterstützt.
Die Implementierung für Linq To Sql auf anderen Datenbankservern
wäre möglich. Jedoch gibt Microsoft die entsprechenden
Spezifikationen dazu nicht frei, da es das hausinterne
Konkurrenzprodukt „Entity Framework“ (kurz EF)
favorisiert (weitere Infos dazu hier)
Im Unterschied zu Linq To Objects und Linq
To DataSet, die bereits bestehende .NET Klassenbibliotheken
aufrüsten, muß bei Linq To SQL für jede
Datenbank eine Zugriffsschicht erstellt werden, die relationale Daten
auf Objekte mit Linq- Operatoren abbildet. Diese Zwischenschicht wird
im Allgemeinen als objektrelationaler Mapper bezeichnet (ORM). ORM's
können manuell erstellt werden (klassische Vorgehensweise).
Stand der Technik ist aber die automatisierte Bereitstellung eines
ORM's für ausgewählte Tabellen und Beziehungen einer
Datenbank (Reverse Engineering). Microsoft stellt in Visual Studio
2008 einen Designer bereit, der den ORM automatisch generiert. Dabei
wird eine XML Datei (*.dbml) aufgebaut, indem die Tabellen von einer
Datenbankverbindung im Serverexplorer auf dem Linq To Sql Classes
Designer gezogen werden. Im
Folgenden das Ergebnis für die ArtikelDB- Beispieldatenbank:
Das
XML- Markup wird wiederum als Vorlage zur Deklaration einer von
System.Linq.DataContext abgeleiteten
Klasse und zu einer Klasse für jeden Entitytyp vom ORM-
Generator genutzt. Die DataContext Klasse sorg schließlich für
die Synchronisierung der Objekte im Programm mit den Datensätzen
in der Datenbank.
Beispiel:
Im
Folgenden wurden die Klassen und Objekte des ORM für die
ArtikelDB genutzt, um ein Geschäftsobjekt für die
Lieferanten zu implementieren:
public class BoLieferanten
{
DtxDBArtikelDataContext _dtx;
public BoLieferanten()
{
// DataContext Objekt für Linq anlegen
_dtx = new DtxDBArtikelDataContext();
}
// Alle Lieferanten
public System.Linq.IQueryable<Lieferanten> select()
{
return _dtx.Lieferanten.AsQueryable<Lieferanten>();
}
// Einen einzelnen Lieferanten
public System.Linq.IQueryable<Lieferanten> select(int lfnr)
{
return _dtx.Lieferanten.Where(L => L.lfnr == lfnr);
}
// Insert
public void insert(int lfnr, string name, string email, string plz)
{
// Neues Lieferantenentity angelegt
Lieferanten l1 = new Lieferanten();
l1.lfnr = lfnr;
l1.name = name;
// Hinzufügen des neuen Lieferantenentitys der Tabelle Lieferanten
_dtx.Lieferanten.InsertOnSubmit(l1);
// Alle änderungen werden in der Datenbank realisiert
_dtx.SubmitChanges();
}
// Update
public void update(int lfnr, string name, string email, string plz)
{
// Das zu ändernde Entity in der Tabelle herausfiltern
Lieferanten l1 = _dtx.Lieferanten.Where(L => L.lfnr == lfnr).First();
l1.name = name;
l1.plz = plz;
l1.email = email;
_dtx.SubmitChanges();
}
// Delete
public void delete(int lfnr)
{
// Das zu löschende Entity in der Tabelle herausfiltern
Lieferanten l1 = _dtx.Lieferanten.Where(L => L.lfnr == lfnr).First();
_dtx.Lieferanten.DeleteOnSubmit(l1);
_dtx.SubmitChanges();
}
}