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:
Database first: Aus
einem bereits existierenden relationalen Datenmodell wird ein
Objektmodell erzeugt.
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)
|
Objekteigenschaft, die auf
anderes Objekt verweist (1:1- Beziehung)
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
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:
Beziehungen
und Navigationseigenschaften im EF
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:
Lazy Loading:
Nachladen der über
Navigationseigenschaften erreichbaren Entites bei Bedarf
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
PLINK nicht möglich !
Mögliche parallele Verarbeitung werden auf dem
Datenbankserver selbst durchgeführt.
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.