Inhaltsverzeichnis         

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();
        }
    }