3 C# 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:
string[] monate = { "jan", "feb", "mar", "apr", "mai", "jun", "jul", "aug", "sep", "okt", "nov", "dez" };
// Linq- Abfrage in Abfragesyntax auf einer Objektmenge
var res = from s in monate where s[0] == 'm' select s;
// Ausgabe des Ergebnisses
foreach (string m in res)
Debug.WriteLine(m);
Beispiel für Erweiterungsmethodensyntax
var res2 = monate.Where(s => s[0] == 'm').Select(s => new {Name= s, NUp= s.ToUpper()});
foreach (var m in res2)
Debug.WriteLine(m.Name + ", " + m.NUp);
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
int[] primz = { 2, 3, 5, 7, 11, 13, 19, 23 };
// Liste einschänken auf Wert > 10
int[] größer10 = primz.Where(p => p > 10).ToArray();
foreach (int p in größer10)
{
Trace.WriteLine(p);
}
Linq offenbart sich hier durch den WhereOperator.
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.
Dictionary<DateTime, string> kalender = new Dictionary<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";
Dictionary<DateTime, string> alle2009 = kalender
.Where(pair => pair.Key.Year == 2009)
.ToDictionary(pair => pair.Key, pair=> pair.Value);
// Projektion mittels Select
int[] listeDerTage = alle2009.Select(pair => pair.Key.Day).ToArray();
// QueryExpression Syntax
int[] listeDerTage2 = (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.
/*
Aggregationen:
Array aller Artikel 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();
// Aggregat: Gesamter Wert des Warenbestandes
decimal gesamtwert = alleArt.Sum(art => art.preis * art.vorrat);
// Aggregat: Geringwertigster Warenposten
decimal minwert = alleArt.Min(art => art.preis * art.vorrat);
// Aggregat: Höchstwertigster Warenposten
decimal maxwert = alleArt.Max(art => art.preis * art.vorrat);
// Aggregat: Durchschnittlicher Wert eines Warenpostens
decimal durchschnittswert = alleArt.Average(art => art.preis * art.vorrat);
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 SelectManyOperator 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.
Zukünftig sollen auch Provider für Sql Serversystem von
Fremdherstellern angeboten werden.
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. 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 ClassesDesigner
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.
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();
}
}