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!
- 0 Kommentar(e)








Mein Kommentar