## 29.4 Verknüpfungen zwischen Entitäten In einer relationalen Datenbank wird die Beziehung zwischen zwei Tabellen mit einem Konzept realisiert, bei dem der Fremdschlüssel einer Tabelle auf den Primärschlüssel einer anderen Tabelle verweist. Dieses Konzept wird in LINQ to SQL mit dem Association-Attribut umgesetzt. Dabei werden beide Seiten einer 1-n-Beziehung beschrieben. Im folgenden Codeausschnitt sind die beiden Tabellen Categories und Products miteinander in Beziehung gesetzt. Categories beschreibt die 1-Seite der Beziehung (Mastertabelle) und Products die n-Seite der Beziehung (Detailtabelle). Auf Code, der nicht im Zusammenhang mit der Beziehung steht, wird in diesem Codefragment komplett verzichtet. Sehen Sie sich zuerst die Entität Category an.
    public class Category {
      private EntitySet<Product> _Products;
      [Association(Name="Categories_Products",
                   Storage="_Products",
                   ThisKey="CategoryID",
                   OtherKey="CategoryID")]
      public EntitySet<Product> Products {
        get {
          return this._Products;
        }
        set {
          this._Products.Assign(value);
        }
      }
    }
Jetzt folgt die Beschreibung der Entität Product.
    public class Product {
      private EntityRef<Category> _Category;
      [Association(Name="Category_Product",
                   Storage="_Categories",
                   ThisKey="CategoryID",
                   OtherKey="CategoryID",
                   IsForeignKey=true)]
      public Category Category {
        get {
          return this._Category.Entity;
        }
        set {
          this._Category.Entity = value;
        }
      }
    }
In beiden der miteinander in Beziehung stehenden Tabellen wird die Beziehung durch ein privates Feld und eine öffentliche Eigenschaft abgebildet. Da eine Instanz vom Typ der Entitätsklasse Product genau einer Kategorie zugeordnet wird, kann im Feld der Product-Instanz die Information der zugeordneten Kategorie gespeichert werden. Ähnliches gilt für eine Instanz der Entität Category. Der Unterschied ist nur der, dass sich einer bestimmten Kategorie mehrere Produkte zuordnen lassen. Dass wir es auf der einen Seite mit einer eindeutigen Zuordnung zu tun haben (eine Kategorie je Produkt), auf der anderen Seite sich aber mehrere Produkte einer Kategorie zuordnen lassen, findet Berücksichtigung im Datentyp der Felder: EntityRef<T> und EntitySet<T>. Beide nehmen im Kontext des Object Relational Mappings eine wichtige Rolle ein. Zur Beschreibung einer Beziehung zwischen zwei Datenbanktabellen sind die beiden Eigenschaften (Category in der Product-Entität und Product in der Category-Entität) mit dem Association-Attribut verknüpft. LINQ to SQL bezieht hieraus alle notwendigen Informationen, um bei Bedarf ein passendes SQL-Statement zu erzeugen, das die erforderlichen Daten aus der Datenbank besorgt. Mit EntityRef<T>, EntitySet<T> und dem Attribut Association wollen wir uns nun etwas genauer beschäftigen. ### 29.4.1 Der Typ »EntityRef<T>« Die Beziehung zwischen den beiden von uns betrachteten Tabellen besteht zwischen der Spalte CategoryID der Tabelle Products und der gleichnamigen Spalte der Tabelle Categories. Jeder Artikel ist einer bestimmten Kategorie zugeordnet. Die Eigenschaft Category der Entität Product speichert die entsprechende Information in einem Feld vom Typ EntityRef<Category>.
    private EntityRef<Category> _Category;
Die Eigenschaftsmethode ist mit dem Attribut Association versehen, dessen Parameter entscheidende Informationen enthalten. ThisKey beschreibt hier die Schlüsselspalte der Entität Products und OtherKey die Schlüsselspalte der in Beziehung gesetzten Entität. Bilden mehrere Spalten den Schlüsselwert, sind die entsprechenden Spaltennamen durch ein Komma getrennt in der Zeichenfolge anzugeben. Mit IsForeignKey=true wird das unter ThisKey genannte Feld zur Fremdschlüsselspalte erklärt.

Tabelle 29.2 Parameter des Attributs »Association«

Parameter | Beschreibung
IsForeignKey | Das Feld ist ein Fremdschlüsselfeld.
Name | Spezifiziert den Namen des Fremdschlüssels.
OtherKey | Bezeichnet die Spalte in der Entität, die auf der anderen Seite der Zuordnung liegt.
Storage | Bezeichnet das Feld, das verwendet wird, um das in Beziehung stehende Entitätsobjekt zu speichern.
ThisKey | Bezeichnet die Eigenschaft, die das lokale ID-Feld enthält. Fehlt dieser Parameter, wird das Feld genommen, dessen Column-Attribut die Angabe IsPrimary enthält.
Die Beschreibung der Beziehung mit dem Association-Attribut vereinfacht die Formulierung einer LINQ-Abfrage. Nehmen wir an, Sie möchten eine Liste aller Artikel ausgeben, die einer bestimmten Kategorie zugeordnet sind. Sie müssen keinen JOIN mehr definieren und können die Eigenschaft Category nutzen, die in Beziehung zur Entität Category steht. Auf der Eigenschaft Category rufen Sie die Primärschlüsselspalte ab und filtern die Ergebnisse wie gewünscht.
    var query = from prod in products
                where prod.Category.CategoryID == 2
                select prod.ProductName;
Hinweis
Das komplette Beispiel finden Sie auf der Buch-DVD unter: ..\Beispiele\Kapitel 29\LINQ_to_SQL\Example_1
Anmerkung
Um die LINQ-Abfrage zu testen, benötigen Sie ein DataContext-Objekt und die Liste aller Produkte. Obwohl wir auf den Typ DataContext erst in Abschnitt 29.6 näher eingehen, möchte ich Ihnen nicht verschweigen, wie diese Klasse einsetzt wird:
    DataContext context = new DataContext(con);
    Table<Product> products = context.GetTable<Product>();
    Table<Category> categories = context.GetTable<Category>();
Zunächst sollten Sie den Namensraum System.Data.Linq mit using bekannt geben. Dem Konstruktor der DataContext-Klasse übergeben Sie eine Verbindungszeichenfolge. Diese gleicht der eines SqlConnection-Objekts von ADO.NET. Das DataContext-Objekt hat mit GetTable<T> eine Methode, deren generischer Typparameter die Angabe einer Entitätsklasse erwartet. Sie rufen GetTable<T> für die Entität Product und – falls erforderlich – auch für Category auf. Der Rückgabewert der Methode GetTable<T> ist vom Typ Table<T> und beschreibt die als Objekte abgebildete Liste aller Datensätze der Tabellen Products und Categories. Der Rückgabewert wird in den Variablen products und categories vorgehalten. products beschreibt hier die Liste, die von unserer LINQ-Abfrage durchlaufen wird.
### 29.4.2 Verzögertes Laden LINQ to SQL arbeitet mit einer Technik, die als verzögertes Laden bezeichnet wird. Das bedeutet, dass die Entität Category erst dann von der Datenbank in den lokalen Speicher geladen wird, wenn darauf lesend oder schreibend zugegriffen wird, zum Beispiel innerhalb einer Schleife:
    foreach (var item in query)
      Console.WriteLine(item);
LINQ to SQL lädt jedoch keine Daten in den Speicher, die sich bereits darin befinden. Deshalb wird vorher intern geprüft, ob sich die entsprechende Category-Entität möglicherweise bereits im Arbeitsspeicher befindet. Ist das der Fall, wird diese Entität genutzt. Müssen Daten von der Datenbank bezogen werden, wird die LINQ-Abfrage von LINQ to SQL in einen SQL-JOIN-Befehl übersetzt, der wie folgt aussieht:
    SELECT [t0].[ProductName]
    FROM [dbo].[Products] AS [t0]
    LEFT OUTER JOIN [dbo].[Categories] AS [t1]
         ON [t1].[CategoryID] = [t0].[CategoryID]
    WHERE [t1].[CategoryID] = 2
### 29.4.3 Der Typ »EntitySet<T>« Vielleicht interessiert Sie, welchen Kategorien mehr als 10 Artikel aus der Tabelle Products zugeordnet sind? Um diese Frage zu beantworten, müssen Sie in der Entität Category eine Eigenschaft definieren, die die zugehörige Gruppe der Product-Entitäten repräsentiert. Das Konzept ähnelt dem aus dem letzten Abschnitt. Allerdings ist eine Product-Entität mit genau einer Category-Entität verknüpft, während eine Category-Entität potenziell mehrere Product-Entitäten beschreiben kann. Daher ist die Eigenschaft, die wir der Entität Category hinzufügen müssen, nicht vom Typ EntityRef<T>, sondern wird durch EntitySet<T> beschrieben.
    public class Categories {
      private EntitySet<Product> _Products;
      ...
    }
Das private Feld wird durch die Eigenschaft Products veröffentlicht, bei der durch das Attribut Association ebenfalls die Verknüpfung der beiden Entitäten beschrieben wird. Dabei kommen dieselben Parameter ins Spiel, die weiter oben schon erläutert worden sind.
    [Association(Name="Category_Product", Storage="_Products",
                 ThisKey="CategoryID", OtherKey="CategoryID")]
    public EntitySet<Product> Products {
      get {
        return this._Products;
      }
      set {
        this._Products.Assign(value);
      }
    }
Mit dieser Ergänzung in der Entität Category können wir die eingangs gestellte Frage mit einer passenden LINQ-Abfrage beantworten:
    var query = from cat in categories
                where cat.Products.Count > 10
                select cat;
    // Ausgabe der Daten
    foreach (var item in query) {
      Console.WriteLine(item.CategoryName);
    }
Hierbei beschreibt categories die Menge aller Kategorien, die durch Category-Entitäten abgebildet werden. Auch die LINQ-Abfrage wird erst in dem Moment ausgeführt, wenn von der Datenbank Daten angefordert werden. Dabei wird ein SQL-Statement erzeugt, das dem folgenden entspricht:
    SELECT [t0].[CategoryID], [t0].[CategoryName]
    FROM [dbo].[Categories] AS [t0]
    WHERE ((
        SELECT COUNT(*)
        FROM [dbo].[Products] AS [t1]
        WHERE [t1].[CategoryID] = [t0].[CategoryID]
        )) > 10
Hinweis
Das komplette Beispiel zu diesem Code finden Sie auf der Buch-DVD unter: ..\Beispiele\Kapitel 29\LINQ_to_SQL\Example_2
### 29.4.4 Ein weiteres Beispiel Möglicherweise wollen Sie aber eine Liste aller Produkte ausgeben lassen, die nach Kategoriezugehörigkeit sortiert ist. Da die Entitätsklassen Product und Category die Verknüpfung der Entitäten beschreiben, ist die LINQ-Abfrage extrem einfach zu formulieren:
    var query = from cat in categories
                select cat;
Jetzt kommt es nur darauf an, aus den vorliegenden Datenformationen diejenigen herauszufiltern, die von Interesse sind. Dabei hilft uns die Eigenschaft Products der Entität Category weiter. Products beschreibt die Liste all jener Artikel, die der betreffenden Kategorie zugeordnet sind. Um Detailinformationen zu jedem Artikel zu erhalten, müssen wir nur die durch die Eigenschaft Products bereitgestellte Liste Element für Element in einer Schleife durchlaufen:
    foreach (var item in query) {
      Console.WriteLine(item.CategoryName);
      foreach (var product in item.Products)
        Console.WriteLine("  {0}", product.ProductName);
      Console.WriteLine(new String('-', 40));
    }
Auch in diesem Codefragment wird die Technik des verzögerten Ladens verwendet. Greifen Sie das erste Mal auf die Products-Eigenschaft einer Kategorie zu (im Code ist das mit product.ProductName der Fall), wird LINQ to SQL die Datenbank abfragen, um die entsprechenden Dateninformationen bereitstellen zu können. ### 29.4.5 Sofortiges Laden der Daten Um wiederholtes Abfragen der Datenbank zu vermeiden, können Sie alle Daten sofort laden. Das kann das Laufzeitverhalten Ihrer Anwendung positiv beeinflussen. Um Daten komplett zu laden, bieten sich Ihnen zwei Möglichkeiten. Die erste gibt explizit die Products-Eigenschaft des Artikels in der Ergebnismenge an:
    var query = from cat in categories
                select new { cat.CategoryName, cat.Products };
Auch wenn diese LINQ-Abfrage wieder sehr einfach zu formulieren ist, ist das SQL-Statement, das zur SQL Server-Datenbank geschickt wird, schon recht komplex:
    SELECT [t0].[CategoryName], [t1].[ProductID],
    [t1].[ProductName], [t1].[CategoryID], (
        SELECT COUNT(*)
        FROM [dbo].[Products] AS [t2]
        WHERE [t2].[CategoryID] = [t0].[CategoryID]
        ) AS [value]
    FROM [dbo].[Categories] AS [t0]
    LEFT OUTER JOIN [dbo].[Products] AS [t1] ON
    [t1].[CategoryID] = [t0].[CategoryID]
    ORDER BY [t0].[CategoryID], [t1].[ProductID]
Die Alternative zu der Angabe der Eigenschaft bietet die Klasse DataLoadOptions. Der Methode LoadWith<T> des DataLoadOptions-Objekts geben Sie über einen Lambda-Ausdruck an, welche untergeordneten Objekte abgerufen werden sollen. Anschließend müssen Sie der Eigenschaft LoadOptions des DataContext-Objekts noch die Referenz auf das DataLoadOptions-Objekt übergeben.
    DataContext context = new DataContext(con);
    Table<Category> categories = context.GetTable<Category>();
    DataLoadOptions dlo = new DataLoadOptions();
    dlo.LoadWith<Category>(cat => cat.Products);
    context.LoadOptions = dlo;
    var query = from cat in categories
                select new { cat.CategoryName, cat.Products };
Angenommen, Sie möchten sich in allen Kategorien die zugehörigen Artikel anzeigen lassen, die mehr als 50 EUR kosten. Den entsprechenden Filter auf die gemappten Daten anzusetzen, würde bedeuten, dass zu viele Daten unnützerweise den Weg von der Datenbank in die Clientanwendung suchen. Besser ist es, den Filter bereits vorher festzulegen. Die Klasse DataLoadOptions bietet diese Möglichkeit. Sie müssen nur vor LoadWith<T> die Methode AssociateWith<T> aufrufen und dort den Filter als Argument angeben.
    DataLoadOptions dlo = new DataLoadOptions();
    dlo.AssociateWith<Category>(
        cat => cat.Products.Where(prod => prod.UnitPrice > 50));
    dlo.LoadWith<Category>(cat => cat.Products);
                context.LoadOptions = dlo;
    var query = from cat in categories
                select new { cat.CategoryName, cat.Products };