Inhaltsverzeichnis         

1 ADO.NET Entity Framework

1.1 Wie die Welt der Objekte auf die Welt der Datensätze abgebildet wird

In objektorientierten Programmen speichern Objekte Informationen als inneren Zustand, auf den sie den Zugriff über Eigenschaften gewähren. Objektmengen werden durch Arrays und Collection implementiert. Indem über die Eigenschaft eines Objektes auf ein anderes verwiesen wird, wird eine 1:1 Beziehung zwischen diesen abgebildet. Der Verweis über eine Eigenschaft eines Objektes auf eine Objektmenge modelliert eine 1:N Beziehung.

Relationale Datenbanken dominieren in der betrieblichen Datenspeicherung. Sie speichern die Daten als Datensätze in Tabellen ab. Mittels Datensatz- Schlüssel und Fremdschlüsselfelder werden Beziehungen in den Tabellensystemen abgebildet.

In folgendem Bild werden die Daten zu einem Planetensystem (Astronomie) als Objektmodell und alternativ als relationales Datenmodell modelliert. Ein objektrelationaler Mapper ist eine Bibliothek, die einen effizienten Datenaustausch zwischen beiden Welten bereitstellt.


Ausschnitt aus einem C#- Programm, das auf dem Objektmodell operiert:

// Datenbank aufbauen
var Sonne = new Himmelkörper() 
            {
               Name = "Sonne",
               Masse_in_kg = 1.989e30,
               Umlaufbahn = null
            };

var Erde = new Himmelköper()
            {
               Name = "Erde",
               Masse_in_kg = 5.974e24
            };

Erde.Umlaufbahn = new Umlaufbahn() 
                  {
                    Zentralobjekt = Sonne,
                    Länge_große_Halbachse_in_km = 149600000,
                    Excentrizität = 0.0167,
                    Umlaufdauer_in_Tagen = 365.256
                  }; 
Sonne.Umkreist_von.Add(Erde);
…

// Abfragen mittels LINQ
var InnerePlaneten = Sonne
                     .Umkreist_von
                     .Where(p => p.Umlaufbahn.Länge_große_Halbachse_in_km < Jupiter.Umlaufbahn.Länge...);

Ausschnitt aus einem SQL- Script, das auf der Datenbank operiert

// Datenbank aufbauen
Declare @line Table(id int)

Insert into dbo.[HimmelskörperTab] (Name, Masse_in_kg)
Output inserted.id into @ID_Sun 
Values ("Sonne", 1.989e30)

Declare @ID_Sun int
Select @id = id from @line

Insert into dbo.[HimmelskörperTab] (Name, Masse_in_kg) Values ("Erde", 5.974e24)

Insert into dbo.[UmlaufbahnenTab] 
(
  Zentralobjekt_ID,
  FK_Himmelskörper_als_Trabant,
  Länge_große_Halbachse_in_km
) 
Values 
(
  99,
  149600000,
  0.0167,
  365.256
)

' Abfrage

Select * from UmlaufbahnenTab 
where Länge_große_Halbachse_in_km < Select Max(Länge_große_Halbachse_in_km)
                                    From UmlaufbahnenTab A Join HimmelkörperTab B 
                                    on A.FK_Himmelskörper_als_Trabant = B.ID
                                             Where B.Name = "Jupiter"

Beim Vergleich der Ausschnitte werden große konzeptionelle und syntaktische Unterschiede deutlich. Der objektrelationale Mapper als Mittler zwischen beiden Welten ist ein komplexes Stück Software !

1.1.1 Entity Framework als objektrelationaler Mapper

Das Entity- Framework (kurz EF) ist ein objektrelationaler Mapper aus dem .NET Framework. Er liegt diesem seit der .NET Version 3.5 bei.




Objektrelationale Mapper sind eine Brücke zwischen relationaler und objektorientierter Welt. Die Brücke kann in zwei Richtungen gebaut werden:

  1. Database first: Aus einem bereits existierenden relationalen Datenmodell wird ein Objektmodell erzeugt.

  2. Modell first: Ein Objektmodell kann mit Designern entworfen werden oder es existiert bereits. Aus diesem wird dann ein relationales Datenmodell erzeugt.

In beiden Fällen wird ein besondere Klasse, der sog. ObjectContext bereitgestellt, welcher die notwendigen Transformationen beim Datenaustausch zwischen relationaler Datenbank und Objektmodell kapselt.

Im Folgenden der wesentliche Ablauf beim Erstellen und Bearbeiten eines EF- Mappers:

1.1.2 Begriffsbildungen im Entity Framework auf höherer Abstraktionsebene

Das EF vermittelt zwischen relationaler - und objektorientierter Welt. Folglich sind Verallgemeinerungen von Begriffen aus beiden Welten notwendig.

Entity Framework- Begriff

Bedeutung im relationalen-

… und im Objektmodell

Entität (engl. Entity)

Datensatz

Objekt

Entity-collection

Tabelle

Liste von Objekten

Zuordnung (engl. Association)

Beziehung (1:1, 1:N)

  1. Objekteigenschaft, die auf anderes Objekt verweist (1:1- Beziehung)

  2. Eigenschaft vom Typ ICollection, die andere Objekte auflistet (1:N- Beziehung)



1.1.3 Vergleich mit Sql To Linq

SQL To Linq war der erste objektrelationale Mapper von Microsoft. EF sollte von Anbeginn SQL To Linq ersetzen, konnte jedoch bezüglich Stabilität und Ausführungsgeschwindigkeit SQL To Linq nicht das Wasser reichen. Ab EF 5.0 kann man aber den Wettstreit zugunsten von EF als entschieden betrachten. Im Folgenden eine Gegenüberstellung beider Bibliotheken:

Feature

Sql To Linq

EF

Datenbank aus Modell erstellen

ja

ja

Modell aus Datenbank erstellen (Reverse Engineering)

ja

ja

Verfügbarkeit für Datenbankserver

nur SQL Server

Verschiedene (SQL- Server, Oracle)

Mapping von Eigenschaften auf Tabellenspalten

  • Namen der Eigenschaften sind anpassbar

  • Typen der Eigenschaften sind anpassbar

  • Namen der Eigenschaften sind anpassbar

  • Typen der Eigenschaften sind anpassbar

  • Mapping kann von Bedingungen abhängig sein

Einschluss der Klassen in Namespaces

Namespace für Kontext und Entities einzeln einstellbar

Entities und Kontext werden nur im Projektnamespace bereitgestellt ! :-(

Komplexität

gering

hoch

Abstraktion von DB

Hoch. Umgang mit Entity- Collections nahezu so wie mit .NET Collections

Gering. Beim Einsatz von Linq To Entities sind viele Einschränkungen des Datenbankservers zu beachten. Z.B. sind die in einer Select- Projektion anwendbaren Funktionen auf den durch System.Data.Objects.SqlClient.SqlFunctions definierten Satz eingeschränkt !


1.2 Die Bausteine von EF

1.2.1 Konzeptuelles Modell

Dem Programmierer präsentiert sich eine Datenbank als konzeptuelles Modell. Dieses beschreibt die Daten als Mengen von Entities, einer von den den Datenbankservern wie MS SQL- Server und Oracle abstrahierten, und damit Datenbank- übergreifenden Form. Formuliert wird das konzeptuelle Modell in einem auf XML basierenden Markup in der EDMX- Datei (Abschnitt edmx:ConceptualModels). Anbei ein Ausschnitt:

<edmx:ConceptualModels>
  <Schema xmlns="http://schemas.microsoft.com/ado/2009/11/edm"
          xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration"
          xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
          Namespace="KeplerDB"
          Alias="Self"
          xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation"
          annotation:UseStrongSpatialTypes="false">

    <!-- Definition eines Container- Objektes als Abstraktion der Datenbank. Es enthält Abstraktionen der
         Tabellen als Entity- Sets.
     -->
    <EntityContainer Name="KeplerDBContainer"
                     annotation:LazyLoadingEnabled="true">

       <EntitySet Name="HimmelskoerperTab"
                  EntityType="KeplerDB.Himmelskoerper" />

          <EntitySet Name="UmlaufbahnenTab"
                     EntityType="KeplerDB.Umlaufbahn" />
          <EntitySet Name="Sterne_Planeten_MondeTab"
                     EntityType="KeplerDB.Stern_Planet_Mond" />

          <AssociationSet Name="TrabantUmlaufbahn"
                          Association="KeplerDB.TrabantUmlaufbahn">

             <End Role="Himmelskoerper"
                  EntitySet="HimmelskoerperTab" />

             <End Role="Umlaufbahnen"
                  EntitySet="UmlaufbahnenTab" />
          </AssociationSet>
          ...
   </EntityContainer>

   <!-- Definition der Entities, welche Datensätze aus Tabellen abstrahieren
    -->
   <EntityType Name="Himmelskoerper">
     <Key>
       <PropertyRef Name="ID" />
     </Key>
     <Property Type="Int32"
               Name="ID"
               Nullable="false"
               annotation:StoreGeneratedPattern="Identity" />
     <Property Type="String"
               Name="Name"
               Nullable="false" />
     <Property Type="Double"
               Name="Masse_in_kg"
               Nullable="false" />
     <NavigationProperty Name="Umlaufbahn"
               Relationship="KeplerDB.TrabantUmlaufbahn"
               FromRole="Himmelskoerper"
               ToRole="Umlaufbahnen" />                                 
     <NavigationProperty Name="Stern_Planet_Mond"
               Relationship="KeplerDB.HimmelskoerperSterne_Planeten_Monde"
               FromRole="Himmelskoerper"
               ToRole="Sterne_Planeten_Monde" />
     ...
     <!-- Definition eines Aufzählungstyps. Dieser kann als Typ eines Attributs in einem Entity im 
          konzeptionellen Modell definiert werden. In MS- SQL- Server wird dieser als Integer dargestellt
      -->
     <EnumType Name="EnumUrlTypen">
       <Member Name="Bild"
               Value="1" />
       <Member Name="WikiPedia"
               Value="2" />
       <Member Name="Allgemein"
               Value="3" />
     </EnumType>
                           
     <!-- Benutzerdefinierte Funktion. Kann in Entity SQL und Linq To Entities eingesetzt werden.
          Definition basiert auf Entity SQL (CAST- Funktion) ! 
      -->
    <Function Name="ConvertToInt32"
              ReturnType="Edm.Int32">
      <Parameter Name="v"
                 Type="Edm.String" />
        <DefiningExpression>
          CAST(v AS Edm.Int32)
        </DefiningExpression>
    </Function>

    <!-- Benutzerdefinierte Funktion. Kann in Entity SQL und Linq To Entities eingesetzt werden.
         Definition basiert auf Entity SQL (Like- Funktion) ! 
    -->
    <Function Name="EndsWith"
              ReturnType="Edm.Boolean">
      <Parameter Name="v"
                 Type="Edm.String" />
      <Parameter Name="postfix"
                 Type="Edm.String" />
      <DefiningExpression>
         v LIKE ('%' + postfix)
      </DefiningExpression>
    </Function>

  </Schema>
</edmx:ConceptualModels>

In Visual Studio wird die EDMX- Datei mit einem Designer angelegt und gepflegt. Dieser ermöglicht eine graphische Darstellung des konzeptuellen Modells:


Ab EF5.0 kann das konzeptuelle Modell auch in reinem C# oder VB.NET Code definiert werden. Ein graphische Darstellung solcher Code first Modelle ist bis Dato (Aug. 2013) nicht möglich.

1.2.1.1 Funktionale Erweiterungen des konzeptuellen Modells

Neben Entities und EntitySets kann das Modell um Funktionen erweitert werden (siehe <Function … >).

<!-- Benutzerdefinierte Funktion. Kann in Entity SQL und Linq To Entities eingesetzt werden.
     Definition basiert auf Entity SQL (Like- Funktion) ! 
 -->
<Function Name="EndsWith"
          ReturnType="Edm.Boolean">
  <Parameter Name="v"
             Type="Edm.String" />
  <Parameter Name="postfix"
             Type="Edm.String" />
  <DefiningExpression>
     v LIKE ('%' + postfix)
  </DefiningExpression>
</Function>

Diese können dann in Abfragespachen wie Entity SQL oder Linq To Entites benutzt werde, die auf dem konzeptuellen Modell möglich sind:

var Laender_die_auf_a_enden = ctx.LaenderTab.Where(r => EdmFunctions.EndsWith(r.Name, "a"));

Basis für die Definition der Funktionen ist Entity SQL, eine vom MS ADO.NET Programmierteam ersonnener SQL – Dialekt. Die im Beispiel eingesetzte LIKE Methode stammt aus Entity- SQL (und nicht aus TSQL, wie man zunächst vermuten würde !).

Um die Funktionen auch in Linq To Enities einsetzen zu können, ist ein Stub zu definieren, der keinerlei Funktionalität in seiner Implementierung besitzt. Denn die eigentliche Implementierung liefert ja das konzeptuelle Modell.

using System.Data.Objects.DataClasses;

namespace DB.Kepler.EF50
{
    public class EdmFunctions
    {
        [EdmFunction("KeplerDB", "ConvertToInt32")]
        public static int ConvertToInt32(string v)
        {
            throw new InvalidOperationException("Only valid when used as part of a LINQ query.");
        }

        [EdmFunction("KeplerDB", "EndsWith")]
        public static bool EndsWith(string v, string postfix)
        {
            throw new InvalidOperationException("Only valid when used as part of a LINQ query.");
        }

    }
}

1.2.2 Speichermodell

Die Implementierung des konzeptionellen Modells auf einem relationalen Datenbanksystem erfolgt durch das Speichermodell. Wie das konzeptionelle Modell wird es in der EDMX Datei (Abschnitt edmx:StorageModels) mit einem speziellen XML- Markup notiert. Viele Strukturen ähneln dem konzeptionellen Modell. Jedoch werden anstelle von Entities und Eigenschaften Tabellen und Tabellenspalten definiert. Dabei werden die auf dem DBMS verfügbaren Typen eingesetzt.

Navigationseigenschaften aus dem konzeptuellen Modell werden mittels Schlüssel/Femdschlüssel implementiert

Konzeptuelles Modell

Speichermodell

<EntityType Name="UrlSammlung">
  <Key>
    <PropertyRef Name="ID" />
  </Key>
  <Property Type="Int32"
            Name="ID"
            Nullable="false"
annotation:StoreGeneratedPattern="Identity" />
  <Property Type="String"
            Name="Kurzbeschreibung"
            Nullable="false" />
  <Property Type="String"
            Name="Url"
            Nullable="false" />
  <NavigationProperty 
            Name="Himmelskoerper"
  Relationship="KeplerDB.HimmelskoerperUrlSammlung"
            FromRole="UrlSammlung"
            ToRole="Himmelskoerper" />
  <Property Type="KeplerDB.EnumUrlTypen"
            Name="UrlTyp"
            Nullable="false" />
</EntityType>
<EntityType Name="UrlSammlungenTab">
  <Key>
    <PropertyRef Name="ID" />
  </Key>
  <Property Name="ID"
            Type="int"
            StoreGeneratedPattern="Identity"
            Nullable="false" />
  <Property Name="Kurzbeschreibung"
            Type="nvarchar(max)"
            Nullable="false" />
  <Property Name="Url"
            Type="nvarchar(max)"
            Nullable="false" />
  <Property Name="UrlTyp"
            Type="int"
            Nullable="false" />
  <Property Name="Himmelskoerper_ID"
            Type="int"
            Nullable="false" />
</EntityType>

1.2.3 Mappings

Durch Mappings werden die Strukturen des konzeptionellen Modells den Strukturen des Speichermodells zugeordnet. Auch die Mappings werden durch ein spezielles XML- Markup in der edmx- Datei beschrieben (Abschnitt edmx:Mappings).

<EntitySetMapping Name="UrlSammlungenTab">
  <EntityTypeMapping TypeName="IsTypeOf(KeplerDB.UrlSammlung)">
    <MappingFragment StoreEntitySet="UrlSammlungenTab">
      <ScalarProperty Name="ID"
                      ColumnName="ID" />
      <ScalarProperty Name="Kurzbeschreibung"
                      ColumnName="Kurzbeschreibung" />
      <ScalarProperty Name="Url"
                      ColumnName="Url" />
      <ScalarProperty Name="UrlTyp"
                      ColumnName="UrlTyp" />
    </MappingFragment>
  </EntityTypeMapping>
</EntitySetMapping>

1.2.4 Entities

Die Datensätze werden durch den objektrelationalen Mapper auf Entities abgebildet.


Felder eines Datensatzes werden dabei zu Eigenschaften, und Beziehungen zu Navigations- und Fremdschlüsseleigenschaften.

1.2.4.1 Skalare Eigenschaften

Über skalare Eigenschaften können die direkt im Entity gespeicherten Informationen abgerufen werden. Skalar bedeutet dabei, das diese Informationen im einzelnen sich integrale Werten sein müssen wie Integer und Strings.

1.2.4.2 Beziehungen. Assoziationen und Navigationseigenschaften

Q:

  1. Beziehungen und Navigationseigenschaften im EF

  2. Beziehungen im EF Designer modellieren

Viele Synonyme: Die Beziehungen zwischen Entities in einem Datenmodell werden auch Assoziationen (engl. associations) oder Zuordnungen genannt.

Multiplizität: Beziehungen werden durch Graphen mit bewerteten Kanten dargestellt:

(Entity_A) 1 --- 0..N (Entity_B)
           \--+-----/
              |
       Beziehung als bewertete Kante zwischen den Knoten A und B

Es wird zwischen 1:1, 1:0..1 und 1:N Beziehungen unterschieden. Steht auf der linken Seite ein Entity mit bis zu N Entities auf der rechten Seite in Beziehung, dann ist die linke Seite der Master (auch Principal), und auf der rechten stehen die Details (auch dependent)

  (Master/Principal) 1 --- 0..N (Detail/Dependent)



Navigationseigenschaften: Im Objektmodell kann vom Master auf die Details und umgekehrt über Navigationseigenschaften zugegriffen werden. Demonstration auf der Übungsdatenbank KeplerDB:

using (var ctx = new DB.Kepler.EF50.KeplerDBContainer())
{
   var Erde = ctx.HimmelskoerperTab.Where(r => r.Name == "Erde").Single();

   // Beim Zugriff auf die Navigationseigenschaften werden 
   // im Hintergrund automat. Abfragen gestartet,
   // die sie nachladen - elegant...
   Assert.AreEqual(Erde.Umlaufbahn.Zentralobjekt.Name, "Sonne");
   Assert.AreEqual(Erde.HimmelskoerperTyp.Name, "Planet");

   // Navigieren in einer 1:N Beziehung: Himmelsköper (Primzipal) auf
   //                                    Umlaufbahnen von Monde (Dependent)
   // Navigieren in 1:1 Beziehung: Zugriff von Mondumlaufbahn über Navi-
   //                              Eigenschaft Trabant auf Mond- Himmelskörper                
   var Saturn = ctx.HimmelskoerperTab.Where(r => r.Name == "Jupiter").Single();
   foreach (var Mond in Saturn.Zentralobjekt_in_Umlaufbahnen
                              .Select(r => r.Trabant))
   {
      Debug.WriteLine("Saturnmond: " + Mond.Name +
                      ", Gewicht in Erdmassen: " +
                     (Mond.Masse_in_kg / Erde.Masse_in_kg).ToString("N4"));
   }

}

Bei 1:1 Beziehungen wird eine Eigenschaft bereitgestellt, die beim Lesen eine Referenz auf das Objekt liefert, mit dem die Beziehung eingegangen wurde.

Bei 1:N Beziehungen wird eine Eigenschaft bereitgestellt, die beim Lesen eine Referenz auf ein Objekt liefert, das ICollection implementiert. Über diese sind dann alle Objekte abrufbar, mit denen die 1:N Beziehung eingegangen wurde.

Beziehungen mit Navigationseigenschaften definieren: Werden in einer Datenbank neue Entities erfasst, die in einer 1:N Beziehung stehen, dann können die Beziehungen zwischen diesen über die Navigationseigenschaften definiert werden. Voraussetzung ist jedoch, dass die Entities bereits der Verwaltung des Contextes unterliegen ! Diese Einschränkung verbietet das Definieren von Beziehungen über Navigationseigenschaften insbesondere in N-Tier Anwendungen (z.B. Webanwendungen, Webdienste). Dort werden Entites weitab vom Context z.B. im Browser oder Webdienstclient erzeugt und konfiguriert. Danach den Server gesendet werden Zwecks einfügen in der Datenbank.

Im Folgenden wird ein Entity für den Planeten Merkur in der Übungsdatenbank KeplerDB angelegt mittels Create(). Dann wird Merkur mit dem Entity TypPlanet in eine 1:N Beziehung gesetzt durch Einfügen in die Navigationseigenschaft Himmelskörper.

using (var ctx = new DB.Kepler.EF50.KeplerDBContainer()) {

 var TypPlanet = ctx.HimmelskoerperTypenTab
                    .Where(r => r.Name == "Planet").Single();

 // Entity für Planet Merkur als Proxy anlegen
 var Merkur = ctx.HimmelskoerperTab.Create();
 Merkur.Name = "Merkur";
 Merkur.Masse_in_kg = mko.Newton.Mass.MassOfMercury.Value;

 // Einfügen des Proxy in Entitycollection ctx.HimmelskoerperTab
 ctx.HimmelskoerperTab.Add(Merkur);

 // Beziehung zwischen HimmeslkörperTyp- Entity (Master/Principal)
 // und Himmelskörper (Detail/Dependent) über Navigationseigenschaft von 
 // Himmelskörpertyp- Etity definieren.
 // Funktioniert nur, da Merkur als Proxy mittels ctx.HimmelskoerperTab.Create()
 // bereits angelegt wurde und damit vom Context verwaltet wird !
 TypPlanet.Himmelskoerper.Add(Merkur);

 // Beziehung kann erst genutzt werden, wenn Änderungen mit der Datenbank
 // synchronisiert wurden.
 ctx.SaveChanges();
}

1.2.4.3 Fremdschlüsseleigenschaften

Im relationalen Modell werden Beziehungen durch Fremdschlüssel und Fremdschlüsseleinschränkungen dargestellt. Die Zuordnung eines abhängigen Entitiy zu seinem Principal, indem die ID des Prinzipal als Fremdschlüssel im abhängigen Entity gesetzt wird, ist simpel und funktioniert auch in N- Tier Anwendungen (z.B. Webanwendungen, Webdienste). Denn zur Definition der Beziehung ist nicht die unmittelbare Anwesenheit eines Kontextes (Entity- Container) notwendig.




Fremdschlüsseleigenschaften werden definiert wie skalare Eigenschaften. Anschließend wird ihre Bedeutung innerhalb einer Beziehung als Fremdschlüssel im EF- Designer ausgedrückt, indem für die betroffene Beziehung in den Eigenschaften eine referentielle Einschränkung festgelegt wird. In dieser Referentiellen Einschränkung ist die Mastertabelle als Principal und der Fremdschlüsseleigenschft als abhängige Eigenschaft zu definieren.

1.2.4.4 Lazy Loading vs. Eager Loading

In einer Datenbank kann es mannigfaltige Beziehungen geben. Über die Navigationseigenschaften kann von einem Entity auf eine Vielzahl weiterer Entities zugegriffen werden, und von diesen über ihre Navigationseigenschaften wieder auf weitere. Im Extremfall kann diese Rekursion zum Abruf einer enorm großen Menge von Datensätze aus einer Datenbank führen. Es ergibt sich das technische Problem, die Menge der Entites zu beschränken, die über Navigationseigenschaften eines abgefragten Entitys mitgeladen werden.

Zwei Strategien werden von EF implementiert:

  1. Lazy Loading: Nachladen der über Navigationseigenschaften erreichbaren Entites bei Bedarf

  2. Eager Loading: Mitladen aller für die weitere Verarbeitung benötigten Entities, die über ausgewählte Navigationseigenschaften erreichbar sind. Dazu werden die betroffenen Navigationseigenschaften in der Abfrage explizit aufgelistet.

Lazy Loading ist elegant, und als Standardverhalten im EF Layer definiert. Jedoch bedeutet Lazy Loading eine erhöhte Arbeitslast auf einem Datenbankserver wenn der Zugriff auf die Navigationseigenschaften dominant ist. Denn jeder Zugriff auf eine Navigationseigenschaft bedeutet jeweils eine zusätzliche Abfrage an den Datenbankserver. Das hat auch zur Folge, das die Navigationseigenschaften eine komplexe Implementierung besitzen, und das Entity unter Verwaltung vom Entity- Container steht. Beispiel:

// Abfrage mit Lazy Loading
using (var ctx = new DB.Kepler.EF50.KeplerDBContainer())
{
   var Erde = ctx.HimmelskoerperTab.Where(r => r.Name == "Erde").Single();

   // Beim Zugriff auf die Navigationseigenschaften werden im Hintergrund automat.
   // Abfragen gestartet, die sie nachladen - elegant...
   Assert.AreEqual(Erde.Umlaufbahn.Zentralobjekt.Name, "Sonne");
   Assert.AreEqual(Erde.HimmelskoerperTyp.Name, "Planet");
}

Eager Loading hat den Vorteil, dass alle Entities, die über die explizit aufgelisteten Navigationseigenschaften in der Abfrage erreichbar sind, zusammen mit den Daten des Entitys in einer einzigen Abfrage vom Server abgerufen werden. Da das Entity samt Navigationseigenschaften nach dem Eager Loading voll initialisiert ist, kann es auch bei unterbrochener Verbindung zum Datenbankserver verarbeitet werden (z.B. N-Tier). Beispiel:

// Abfrage mit Eager Loading
using (var ctx = new DB.Kepler.EF50.KeplerDBContainer())
{
   ctx.Configuration.LazyLoadingEnabled = false;
   //ctx.Configuration.ProxyCreationEnabled = true;

   var Erde = ctx.HimmelskoerperTab.Where(r => r.Name == "Erde").Single();

   // Navigationseigenschaften sind leer, da beim Abfragen diese nicht mitgeladen wurden
   Assert.IsNull(Erde.Umlaufbahn);
   Assert.IsNull(Erde.HimmelskoerperTyp);

   // Mitladen der Navigationseigenschaften .Umlaufbahn und .HimmelskörperTyp wird veranlasst
   var Erde2 = ctx.HimmelskoerperTab.Include("Umlaufbahn")
                                    .Include("HimmelskoerperTyp")
                                    .Where(r => r.Name == "Erde").Single();

   // Da die Navigationseigenschaft .Umlaufbahn.Zentralobjekt nicht mittels Include 
   // zum Mitladen markiert wurde, ist diese leer
   Assert.IsNull(Erde2.Umlaufbahn.Zentralobjekt, "Sonne");
   Assert.AreEqual(Erde2.HimmelskoerperTyp.Name, "Planet");

   // Jetzt wird auch .Umlaufbahn.Zentralobjekt für das Mitladen markiert ...
   var Erde3 = ctx.HimmelskoerperTab.Include("Umlaufbahn")
                                    .Include("Umlaufbahn.Zentralobjekt")
                                    .Include("HimmelskoerperTyp").Where(r => r.Name == "Erde").Single();

   // ... und ein Zugriff auf diese ist erfolgreich
   Assert.AreEqual(Erde3.Umlaufbahn.Zentralobjekt.Name, "Sonne");
}

1.2.4.5 Entity- Zustände

Wird ein Entity im Client bearbeitet, so kann es dabei mehrere Zustände durchlaufen:


Wie der Zustand eines Entity implementiert ist, entscheidet die verwendete T4- Vorlage.

1.2.4.6 Entity Basisklasse bis EF 5.0

Bis EF 4.0 mussten alle Entity- Klassen von der Basisklasse EntityObject erben. Diese implementiert bereits den Entity- Zustand (EntityState).


Über die RelationShip- Manager Eigenschaft stellt diese Basisklasse die Basisfunktionalität für Navigationseigenschaften bereit.

1.2.4.7 Entities als Pocos ab EF 5.0

Ab Visual Studio 2012 werden die Entity- Klassen mit der vorinstallierten T4- Vorlage EF 5.x DbContext- Generator erzeugt. Dabei werden diese als sog. POCO's implementiert (plain old clr objects).

POCO's leiten sich von keiner speziellen Basisklasse ab. Sie bestehen nur aus Eigenschaften. Beispiel:

  
    
      /
    
  /------------------------------------------------------------------------------
// <auto-generated>
//    Dieser Code wurde aus einer Vorlage generiert.
//
//    Manuelle Änderungen an dieser Datei führen möglicherweise zu unerwartetem Verhalten Ihrer Anwendung.
//    Manuelle Änderungen an dieser Datei werden überschrieben, wenn der Code neu generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------

namespace DB.Kepler.EF50
{
    using System;
    using System.Collections.Generic;
    
    public partial class Himmelskoerper
    {
        public Himmelskoerper()
        {
            this.Zentralobjekt_in_Umlaufbahnen = new HashSet<Umlaufbahnen>();
            this.UrlSammlung = new HashSet<UrlSammlung>();
        }
    
          // Eigenschaften, die mit den Spalten der Datensätze in der Tabelle korrespondieren
        public int ID { get; set; }
        public string Name { get; set; }
        public double Masse_in_kg { get; set; }

        // Navigationseigenschaften    
        public virtual Umlaufbahnen Umlaufbahn { get; set; }
        public virtual ICollection<Umlaufbahnen> Zentralobjekt_in_Umlaufbahnen { get; set; }
        public virtual Sterne_Planeten_Monde Sterne_Planeten_Monde { get; set; }
        public virtual Raumschiffe Raumschiffe { get; set; }
        public virtual ICollection<UrlSammlung> UrlSammlung { get; set; }
        public virtual HimmelskoerperTyp HimmelskoerperTyp { get; set; }
    }
}

POCO's liefern keine Funktionen zur Implementierung von Navigationseigenschaften wie die EntityObject- Basisklasse. Um trotzdem Navigationseigenschaften und Lazy- Loading zu implementieren, werden die Navigationseigenschaften in den POCO's als polymorphe Eigenschaften (virtual) angelegt. Das ermöglicht dem Framework ab Version 4.0, von den POCO's abgeleitete Klassen und Entityobjekte zu generieren, genannt Proxies. Diese Implementieren die Navigationseigenschaften !

1.2.4.7.1 Proxies zur Implementierung von Change Tracking und Lazy Loading mittels POCOS

Wenn Entities als POCO's implementiert sind, dann liefern Abfragen auf den EntityCollections des Datenkontextes Aufzählungen von Entity- Proxy- Objekten zurück:


Die Typen dieser Proxies leiten sich von den POCO-Klassen ab. Sie implementieren die polymorphen Navigationseigenschaften so, dass Lazy Loading als auch Change- Tracking möglich wird.

Soll schon beim Anlegen eines neuen Entitys anstelle des POCO- Objektes ein Proxy zum Einsatz kommen, dann kann dieses mittels der Methode Create(), die jede Entitycollection besitzt erzeugt werden:

// Anlegen als POCO
//var Merkur = new Himmelskoerper() { Name = "Merkur", Masse_in_kg = mko.Newton.Mass.MassOfMercury.Value };

// Anlegen als Proxy
var Merkur = ctx.HimmelskoerperTab.Create();
Merkur.Name = "Merkur";
Merkur.Masse_in_kg = mko.Newton.Mass.MassOfMercury.Value;

// Hinzufügen zur Entity-Collection
ctx.HimmelskoerperTab.Add(Merkur);
1.2.4.7.2 Verzicht auf Proxies beim Eager Loading

Beim Eager Loading kann man auch die Erzeugung von Proxies abschalten. Dann liefern Abfragen Aufzählungen von Entities, die vom Typ der POCO- Klassen sind:


Die Erzeugung von Proxies muss, wie das Beispiel zeigt, explizit abgeschaltet werden durch Setzen der Eigenschaft Configuration.ProxyCreationEnabled auf false.

1.2.5 Entity- Container = Kontext

Die Entities werden im Client vom Objektkontext verwaltet. Dieser lädt die Entities bei Bedarf (Lazy Loading) aus der Datenbank nach. Auch bietet er Methoden an, um Änderungen an den Entities mit der Datenbank zu synchronisieren.

1.2.5.1 Basisklasse vom Entity- Container bis EF 5.0




1.2.5.2 Basisklasse vom Objektkontext ab EF 5.0

Ab EF 5.0 wird der Objektkontext von der Basisklasse DbContext abgeleitet.




1.2.5.2.1 DBContext.Database Eigenschaft

Über die Database- Eigenschaft werden Funktionen zum Anlegen und Löschen von Datenbanken auf einem System angeboten. Zudem besteht die Möglichkeit, SQL- Kommandos direkt an die Datenbank zu senden.

1.2.5.2.1.1 Datenbank mittels DBContext anlegen

Folgender Programmausschnitt demonstriert das Anlegen einer DB (Model first).

// Anlegen der Testdatenbank mit dem Schema von KeplerDB
try
{
   using (var ctx = new DB.Kepler.EF50.KeplerDBContainer())
   {
      // Datenbank erzeugen
      if (ctx.Database.Exists())
        ctx.Database.Delete();

      Assert.IsFalse(ctx.Database.Exists());

      ctx.Database.Create();

      Assert.IsTrue(ctx.Database.Exists());
    }
} catch(Exception ex) { Assert.Fail()}
1.2.5.2.1.2 SQL- Befehl direkt an DB senden

Folgender Programmausschnitt demonstriert, wie ganze Tabellen geleert werden durch direktes Absenden von SQL- Befehlen an die DB.

public static void ClearLaender()
{
  try
  {
     using (var ctx = new KeplerDBContainer())
     {
       ctx.Database.ExecuteSqlCommand("delete from dbo.LaenderTab");
     }
  }
  catch (Exception ex)
  {
     throw new KeplerBackupException("ClearLaender", ex);
  }
}
1.2.5.2.2 DBContext.Configuration Eigenschaft

Über die Configuration- Eigenschaft kann




1.3 LINQ To Entities

Linq to Entities ist eine Implementierung von Linq, welche Linq- Ausdrücke in die Abfragesprache des Datenbankservers übersetzt.

Der Einsatz erfolgt, indem direkt auf die Entitycollections Linq- Abfragen angewendet werden:

Die Auflistungen im Objektkontext sind mittels Linq- Ausdrücke filterbar. Folgende Einschränkungen sind zu beachten

  1. PLINK nicht möglich !

    Mögliche parallele Verarbeitung werden auf dem Datenbankserver selbst durchgeführt.

  2. Funktionen, die in einer Select- Prokjektion oder Where Klausel eingesetzt werden, müssen in TSQL übersetzbar sein !

    Z.B. scheitert die Konvertierung eines int in einen String mittels .ToString():

    ctx.LaenderTab.Select(r => r.ID.ToString() + "," + r.Laenderkennzeichen + "," + r.Name); // Laufzeitfehler ! Keine Entsprechung für ToString in TSQL.

    Stattdessen muss eine Funktion aus System.Data.Object.SqlClient.SqlFunctions eingesetzt werden:

    ctx.LaenderTab.Select(r => System.Data.Objects.SqlClient.SqlFunctions.StringConvert((decimal)r.ID).Trim() + "," + r.Laenderkennzeichen + "," + r.Name);

    Die Funktionen aus SqlFunctions haben nur den Zweck, TSQL- Funktionen im Linq To Entities Ausdrucksbaum darzustellen. Ihr direkter Einsatz im .NET Client außerhalb eines Linq- Ausdruckes ist verboten.

    Alternativ kann das Problems in LINQ To Objects gelöst werden, indem zuvor mittels Konvertierung wie .ToEnumerable() die Datensätze auf den Client heruntergeladen wurden. Bei großen Resultsets kann das aber eine sehr schlechte Idee sein...

1.3.1 Abfrage vs. Erweiterungsmethodensyntax

Generell kann eine Abfrage in C# oder VB.Net in der Abfragesyntax oder Erweiterungsmethodensyntax formuliert werden.

Die Abfragesyntax ähnelt SQL. Diese vertraute Syntax wird als Vorteil verkauft, und lockt Interessierte an. Jedoch ist die Abfragesyntax innerhalb von C# und VB.NET ein Stilbruch. Schwerer wiegt jedoch, dass das Formulieren von Abfragen mit der Erweiterungsmethodensyntax einfacher gelingt als mit der Abfragesyntax, insbesondere bei komplexen Abfragen.

1.3.2 Where

Where(Kriterium) schränkt eine Menge von Entities auf jene ein, die das Kriterium erfüllen. Das Kriterium ist ein Lambda- Ausdruck. In diesem dürfen nur Operatoren eingesetzt werden, die auf die das SQL des Datenbankservers abbildbar sind. Folgende Tabelle listet einige zulässige Vergleichsoperationen auf:

Semantik der Operation

C#

TSQL

Vergleich auf Identität

r => r.A == r.B

A = B

Ungleich

r => r.A != r.B

A <> B

Zeichenkette beginnt mit

r => r.A.StartWith("Am Anfang")

A Like 'Am Anfang%'

Änlicher Zeichenkettenausdruck

r => r.A.Contains("Gold")

oder

// PatIndex aus System.Data.Objects.SqlClient.SqlFunctions

r => PatIndex("Gold", r.A) > 0

A Like '%Gold%'

Datumsvergleiche mit Funktionen aus System.Data.Objects.SqlClient.SqlFunctions

// z.B. Alle Start- Daten vor 1970

r => DateDiff("year", r.Start) < 1970

DateDiff(year, Start) < 1970

1.3.3 Select

Das Resultset eine Abfrage kann durch Select in eine Liste aus Datensätze umgewandelt, deren Format den Wünschen des Anwenders entspricht. In SQL wird dieser Prozess als Projektion bezeichnet.

Analog Where können auch in Select nur Operatoren und Funktionen eingesetzt werden, die sich in TSQL übersetzen lassen. In der Klasse System.Data.Object.SqlClient.SqlFunctions sind die in TSQL verfügbaren Funktionen definiert.