Zur Zeit wird gefiltert nach: ctp5
Filter zurücksetzen
Entity Framework 4.1 – Aus der CTP 5 wird EF 4.1
Die Code First-Variante verpasst dem Entity Framework eine neue Version und wird als eigenständiges Update kommen. Ganz bewusst wird hier auf die Kombination mit dem Service Pack 1 verzichtet. Somit wird Code First noch im ersten Quartal bereitgestellt werden.
In meinen Vortrag von der VSone habe ich bereits die Pluggable Conventions verwendet, als Möglichkeit den Konfigurationsaufwand für TPT zu verringern. Diese Pluggable Conventions schaffen es nicht ins EF 4.1. Die Beispiele werden dementsprechend nicht mehr funktionieren.
Was mit dem EF 4.1 auch in DB First und Model First Einzug halten wird, ist die Validierung mit den DataAnnotations. Das finde ich sehr hilfreich, da es mir einen roten Faden im Bezug auf die Validierungsstrategie aufzeigt und unnötige Datenbankanfragen minimieren kann.
Was mit EF 4.1 Code First noch nicht unterstützt wird, sind Stored Procedures. Hier muss man sich im Hinterkopf behalten, was ich in diesem Beitrag aufgezeigt habe. Am besten zeichnet man vor Projektbeginn ein Umweltdiagramm, um alle Stakeholder ermitteln zu können. Gehört zwar zur alten Schule und macht auch fast niemand, aber es hat sich in meinen Augen bewährt.
Weitere Details gibt es im ADO.NET team blog.
Entity Framework 4 CTP5 - Edmx-Datei vom DbContext erstellen oder wenn ein Methodenwechsel notwendig wird
Der Code First-Ansatz hat seine Reize. Mit den verbesserten Funktionalitäten, trotz kleiner Bugs, geht es auch zügig von der Hand. Mit der CTP5 hat auch eine T4 Vorlage Einzug gehalten, dass die Möglichkeit bietet, mit dem EDMX-Designer den DbContext zu nutzen. Einfach ausgedrückt ist der DbContext ein Wrapper auf den ObjectContext, der das Mapping bzw. den Umgang mit dem EDMX-File vereinfacht.
Dabei muss auch erwähnt werden, das Paradigmen, die einfach und locker von der Hand gehen, gerne verwendet werden. Die Codezentrierte Anwendungsentwicklung, als Vertreter des Top down-Ansatzes, kann aber in Konflikt mit einzelnen Stakeholdern stehen. Problematisch wird es, wenn man mit dem Top down-Ansatz begonnen hat und in einer späteren Phase Anforderungen hinzukommen, die aktuell mit der CTP5 nicht realisierbar sind. Zudem kann die Gefahr bestehen, dass die Top down Vorgehensweise zu einem Meet in the middle-Ansatz mutiert.
Ein mögliches Szenario, bei dem die Mapping-Philosophie an die Grenzen stösst, ist die Verwendung von Prozeduren und Views für die CRUD-Funktionalitäten, was mit den ObjectContext problemlos realisierbar ist. Mit Workarounds können diese Prozeduren für das Speichern, Ändern und Löschen verwendet werden, aber den Code möchte ich nicht warten.
Wenn ein CSO, Beauftragter für Datensicherheit oder ein DBA diese Anforderung in einer späteren Phase verlangt, wie es in der Realität üblich ist, stösst man mit dem Code First-Ansatz in der CTP5 sehr schnell an die Grenzen des machbaren. Aktuell ist hier das Mapping von Prozeduren oder Sichten nicht möglich und eine Variante wäre der Wechsel zum meist "unbeliebten" Bottom up. Der Vorteil für den Entwickler, es können Aufgaben an den DBA delegiert werden und die Gefahr in den Meet in the middle-Ansatz zu fallen ist ebenfalls minimiert.
Machen wir also ein kleines Beispiel, das völlig sinnfrei ist. Zuerst erstellen wir die Klassen:
Klassen (C#)
public abstract class AbstractEntity01
{
public int Id { get; set; }
[StringLength(100), Required]
public string AbstractEntity01Field1 { get; set; }
[StringLength(100), Required]
public string AbstractEntity01Field2 { get; set; }
}
public abstract class AbstractEntity02 : AbstractEntity01
{
public int Id { get; set; }
[StringLength(100), Required]
public string AbstractEntity02Field1 { get; set; }
[StringLength(100), Required]
public string AbstractEntity02Field2 { get; set; }
}
public abstract class AbstractEntity03 : AbstractEntity02
{
public int Id { get; set; }
[StringLength(100), Required]
public string AbstractEntity03Field1 { get; set; }
[StringLength(100), Required]
public string AbstractEntity03Field2 { get; set; }
}
public abstract class AbstractEntity04 : AbstractEntity03
{
public int Id { get; set; }
[StringLength(100), Required]
public string AbstractEntity04Field1 { get; set; }
[StringLength(100), Required]
public string AbstractEntity04Field2 { get; set; }
}
public class Sample : AbstractEntity04
{
public int Id { get; set; }
[StringLength(100), Required]
public string TestField3 { get; set; }
[StringLength(100), Required]
public string TestField4 { get; set; }
}
public class Navigation01
{
public int Id { get; set; }
[StringLength(100)]
public string TestField3 { get; set; }
[StringLength(100)]
public string TestField4 { get; set; }
public int SampleId { get; set; }
public Sample Sample { get; set; }
}
public class Navigation02
{
public int Id { get; set; }
[StringLength(100)]
public string TestField3 { get; set; }
[StringLength(100)]
public string TestField4 { get; set; }
public int SampleId { get; set; }
public Sample Sample { get; set; }
}
Anschliessend den DbContext:
DbContext (C#)
public class VoelligSinnfreiContext : DbContext
{
public IDbSet<AbstractEntity01> AbstractEntities01 { get; set; }
public IDbSet<Navigation01> Navigations01 { get; set; }
public IDbSet<Navigation02> Navigations02 { get; set; }
}
Jetzt besteht das Problem, dass der DBA mit Unterstützung des CSO und des Beauftragten für Datensicherheit, in einer späteren Phase gespeicherte Prozeduren und Sichten fordert. Natürlich ist das Berechtigungskonzept angepasst und der Test der CRUD-Funktionalität funktioniert bedingt durch die geänderten Anforderungen nicht mehr:
Mit dem Meet in the Middle-Ansatz würde man jetzt den Versuch starten, mithilfe von DbContext..SqlQuery und SqlCommand und manuellen Mapping die Funktionalität wieder zum laufen zu bekommen.
Beim Mapping auf die View würde es wie folgt aussehen:

- Abbildung 3 Manuelles Mapping auf die Sicht vwSample
Wenn der Entwickler nun wieder die Berechtigung erhält, die Datenbank zu erstellen kommt es schon zum ersten Problem: Während der Erstellung wird dann aus der Sicht eine Tabelle. Bei dieser Vorgehensweise rutscht man also vom ursprünglich geplanten Top down-Ansatz in den Meet in the middle-Ansatz. Das Mapping der Prozeduren wird dann richtig spannend, da sich der Entwickler neben der Handarbeit des Mappings mithilfe von SqlCommand auch um die State-Aktualisierung bei Create/Update/Delete kümmern muss.
Eine andere Variante wäre jetzt der Wechsel zum Bottom up-Ansatz und die Verwendung des EDM-Designers. Dazu benötigen wir natürlich eine EDMX-Datei. Aus dem DbContext heraus kann diese wie folgt erstellt werden:
DbContext (C#)
public class VoelligSinnfreiContext : DbContext
{
public IDbSet<AbstractEntity01> AbstractEntities01 { get; set; }
public IDbSet<Navigation01> Navigations01 { get; set; }
public IDbSet<Navigation02> Navigations02 { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
this.CreateEdmFile(modelBuilder);
base.OnModelCreating(modelBuilder);
}
protected void CreateEdmFile(System.Data.Entity.ModelConfiguration.ModelBuilder model)
{
string path = @"Pfad";
var edm = model.Build(new DbProviderInfo("System.Data.SqlClient", "2008"));
using (var writer = new XmlTextWriter(Path.Combine(path, "VoelligSinnfreiContext.edmx"),
Encoding.UTF8))
{
edm.WriteEdmx(new DbProviderInfo("System.Data.SqlClient", "2008"), writer);
}
}
}
Mit dieser Variante kann aktuell das Mapping von Sichten und Prozeduren einfacher vorgenommen werden und dieser Kompromiss kann einen ebenfalls viel "Gefrickel" und Ärger ersparen.
Das Mapping der Prozeduren erfolgt nun auf Ebene der Sample-Entity, zuvor müssen diese jedoch dem Modell hinzugefügt werden.
Jetzt muss nur noch die Codegenerierungsvorlage angepasst werden, damit die bestehende Arbeit nicht vom Prozess der Codegenerierung vernichtet wird. Die T4-Vorlage für den DbContext heisst ADO.NET DbContext Generator.
Um die geleistete Arbeit nicht zu vernichten, wird der EntityFrameworkFileTemplateManager durch den FileTemplateManager ersetzt und definiert, dass bestehende Dateien nicht überschrieben werden sollen.

- Abbildung 7 Anpassung der Code-Generierungsvorlage, damit die bestehenden Klassen nicht vernichtet werden
Die Funktionalität ist also wieder gewährleistet.
Im Bezug auf die Prozeduren, hier sind für die Zukunft bereits Anpassungen geplant. Mit dem DbContext soll es eines Tages möglich sein, diese gleich mit zu erstellen. Wann es so weit sein wird, weiss ich aber auch nicht. Wenn dieser Punkt realisiert ist, wäre der Wechsel zurück ohne Probleme möglich.
Aber auch der Ansatz mit der Erstellung des EDMX-Files hat so seine Ecken und Kanten. Dieser funktioniert nur einwandfrei, wenn man völlig auf die Konventionen setzt. Aktuell kommt es zu Ausnahmen, wenn die manuellen Mappings berücksichtigt werden sollen.
Eine andere Alternative wäre natürlich auch der Wechsel auf einen anderen O/R Mapper, wenn die nötige Zeit dafür besteht. Aber auch hier gilt: Vorsicht vor Meet in the middle!
Folien und Beispiele der VSone Session Inheritance Strategien mit dem Entity Framework
Mit diesem Beitrag werden die Beispiele nachgeliefert. Im Zip-File befinden sich auch die Datenbankskripts, die bedingt durch die enthaltenen Testdaten etwas gross sind.
Bevor die Skripts ausgeführt werden, müssen die Datenbanken manuell angelegt werden.
Eine Warnung zum Extrem-Beispiel mit dem Span-Query, es ist definitiv nicht zur Nachahmung für produktive Projekte empfohlen, es soll nur die möglichen Workarounds mit dem Kontext-Cache aufzeigen, um das Ganze optimieren zu können. Für die Optimierung in Verbindung mit Paging war die Zeit leider zu knapp.
Materialen
Blogbeiträge passend zum Vortrag
- http://www.databinding.net/nc/blog/post/2010/09/22/entity-framework-4-sql-optimierung-mit-query-rewrite.html
- http://www.databinding.net/nc/blog/post/2010/06/04/entity-framework-4-sql-abfragen-direkt-ausfuehren.html
- http://www.databinding.net/nc/blog/post/2010/08/22/eftracingprovider-und-die-notsupportedexception-bei-executestorequeryt.html
- http://www.databinding.net/nc/blog/post/2010/09/18/eftracingprovider-sql-log-im-database-engine-tuning-advisor-deta-verwenden.html
- http://www.databinding.net/nc/blog/post/2010/09/01/entity-framework-4-model-first-der-tph-strategie-mit-hilfe-des-entity-designer-database-generation.html
- http://www.databinding.net/nc/blog/post/2010/09/14/entity-framework-4-self-tracking-entities-mit-eftracing-und-efcachingprovider-unterstuetzung.html
- http://www.databinding.net/nc/blog/post/2010/10/03/entity-framework-4-query-rewrite-mit-eftracingprovider-simulieren.html
Entity Framework 4 CTP5 – ComplexType by Convention
In einem Beispiel zu T4 habe ich mit einem einfachen Klassendiagramm und T4 den Code erstellen lassen. Für komplexe Typen habe ich dabei die Klasse ComplexTypeConfiguration<T> verwendet.
Bei der Klasse Address handelt es sich dabei um einen komplexen Typ. Dieser soll innerhalb der Tabelle der Klasse Customer persistiert werden.
Mit folgendem Mapping lässt sich dies erreichen:
Mapping ComplexType (C#)
internal class AddressConfiguration : ComplexTypeConfiguration<Address>
{
public AddressConfiguration()
{
this.Property(x => x.Street).IsRequired().IsVariableLength().HasMaxLength(80).IsUnicode();
this.Property(x => x.City).IsRequired().IsVariableLength().HasMaxLength(100).IsUnicode();
this.Property(x => x.PostalCode).IsRequired().IsVariableLength().HasMaxLength(10).IsUnicode();
}
}
Wie diese Konfiguration dem DbContext hinzugefügt wird, ist im Beitrag DB First mit dem DbContext beschrieben.
Eine weitere interessante Möglichkeit ist das Mapping bei Konventionen. Wenn die Address-Klasse keine Id-Eigenschaft besitzt und in einer anderen Klasse wie zum Beispiel Customer verwendet wird, greift neu die Konvention und die Eigenschaften werden innerhalb der Customer-Tabelle angelegt.
Ein kleines Beispiel:
Klasse Customer (C#)
[MetadataType(typeof(CustomerMetadata))]
public class Customer
{
public int Id { get; internal set; }
public string Name { get; set; }
public Address OfficeAddress { get; internal set; }
}
Klasse Address (C#)
///[MetadataType(typeof(AddressMetadata))]
public class Address
{
public string Street { get; set; }
public string Postalcode { get; set; }
public string City { get; set; }
}
Die Kunden-Klasse enthält nun die Adresse als eigenständigen Typ. Der DbContext hat dabei folgenden Aufbau:
Notwendiger DbContext für Szenario (C#)
public class TestContext : DbContext
{
public DbSet Customers { get; set; }
}
Nach der Erstellung der Datenbank enthält die Tabelle Customers auch die Eigenschaften der Klasse Address.

- Abbildung 2 Mapping Mehere Klassen auf eine Tabelle
Bei genauer Betrachtung der Abbildung 2 fällt aber auf, dass die Datentypen wieder eine Länge von 4000 Zeichen haben. Im Code wird zudem ersichtlich, dass das MetadataAttribute für die Klasse Address auskommentiert ist.
Das liegt in einem Bug begründet. Werden die DataAnnonations auf Metadaten-Ebene verwendet, so begrüsst einen folgende Fehlermeldung:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
- System.Data.Edm.EdmEntityType: : EntityType 'Address' has no key defined. Define the key for this EntityType.
- System.Data.Edm.EdmEntitySet: EntityType: The EntitySet Addresses is based on type Address that has no keys defined.
Die Fehlermeldung sagt mir also, dass ein Schlüssel erwartet wird. Einen Workaround dafür gibt es auch, einfach verwenden wie in der CTP 4 bzw. der Klasse Address das ComplexType-Attribut verpassen . ;-)
Entity Framework 4 CTP 5 – Braucht es das NotMapped-Attribut überhaupt?
Beim CodeFirst-Ansatz hat das NotMapped-Attribut die Aufgabe Eigenschaften für das O/R-Mapping zu ignorieren. Notwendig wird dieses, wenn Auto-implemented properties oder Eigenschaften verwendet werden, die den Getter und Setter implementieren.
Interessant wird es, wenn Eigenschaften nur den Getter implementieren (Read only properties). In diesem Szenario ist die Kennzeichnung mit NotMapped nicht notwendig.
Dazu ein kleines Beispiel mit der Customer-Klasse.
Customer-Klasse mit Read-only Eigenschaft (C#)
[MetadataType(typeof(CustomerMetadata))]
public class Customer
{
public int Id { get; internal set; }
public string Name { get; set; }
public string Street { get; set; }
public string Postalcode { get; set; }
public string City { get; set; }
public string LetterAddress
{
get
{
return String.Format(
"{0}\r\n{1}\r\n{2} {3}",
this.Name,
this.Street,
this.Postalcode,
this.City);
}
}
}
Neu besitzt die Klasse eine Eigenschaft mit den Namen LetterAddress, die nur einen Getter implementiert. Nach der Neuerstellung der Datenbank hat die Tabelle auf der Datenbank den Aufbau wie in Abbildung 1.

- Abbildung 1 Aufbau der Tabelle auf Datenbankebene ohne Spalte LetterAddress
Dabei wird ersichtlich, dass Eigenschaften die nur einen Getter implementieren während der Erstellung ignoriert werden. Sobald der Setter ins Spiel kommt, ist das Attribut NotMapped weiterhin notwendig.







Social Bookmarking