Schon seit einigen Versionen existieren in Entity Framework Komponenten, die der Entwickler austauschen kann, um das Standardverhalten anzupassen. Bis dato musste er die zu verwendenden Komponenten über statische Eigenschaften und Methoden registrieren. Ab Version 6 existiert nun auch die Möglichkeit einer zentralen Konfigurationsklasse, die die gewünschten Komponenten an einer zentralen Stelle registriert. Darüber hinaus sind mit Entity Framework 6 weitere austauschbare Komponenten dazugekommen.
Dieser Artikel wirft einen Blick auf einige dieser Komponenten, die nicht nur für Anbieter von Datenbankprovidern, sondern auch für Anwendungsentwickler interessant sein können.
Datenbank auf bestimmte Version migrieren
Entity Framework Code First bietet seit seinen ersten Tagen die Möglichkeit, eine Datenbank bei Verwendung des Kontexts zu initialisieren. Um hiervon Gebrauch zu machen, legt der Entwickler einen so genannten Database Initializer fest. Entity Framework bietet die folgenden Database Initializer an, wobei der Name jeweils Programm ist: DropCreateDatabaseAlways, DropCreateDatabaseIfModelChanges, CreateDatabaseIfNotExists, MigrateDatabaseToLatestVersion. Letzterer migriert die Datenbank unter Verwendung von Migrations auf die aktuellste Version, sofern sie nicht ohnehin schon in dieser vorliegt.
Um einen eigenen Database Initializer bereitzustellen, implementiert der Entwickler das Interface IDatabaseInitializer
Der Konstruktor der betrachteten Implementierung nimmt die Version entgegen, auf die zu migrieren ist, und instanziiert TMigrationsConfiguration. Die Methode InitializeDatabase ist der Dreh- und Angelpunkt der diskutierten IDatabaseInitializer-Implementierung, zumal sie Entity Framework zur Initialisierung der Datenbank aufruft. Sie führt unter Verwendung der Klasse DbMigrator die Migration auf die angegebene Zielversion aus.
public class MigrateDatabaseToSpecifictVersion: IDatabaseInitializer where TContext : DbContext where TMigrationsConfiguration : DbMigrationsConfiguration , new() { private DbMigrationsConfiguration config; private string version; public MigrateDatabaseToSpecifictVersion(string version) { this.version = version; this.config = new TMigrationsConfiguration(); } public void InitializeDatabase(TContext context) { var dbMigrator = new DbMigrator(this.config); dbMigrator.Update(version); } }
Damit Entity Framework diesen Database Initializer auch verwendet, ist der Entwickler dazu angehalten, ihn zu registrieren. Dies geschieht entweder über eine statische Methode oder ab Version 6 auch unter Verwendung der Methode SetDatabaseInitializer (Listing 2) innerhalb einer zentralen Konfigurationsklasse, die von DbConfiguration erbt.
public class CustomDbConfiguration : DbConfiguration { public CustomDbConfiguration() { SetDatabaseInitializer(new MigrateDatabaseToSpecifictVersion("v1")); } }
Verbindung zur Datenbank aufbauen
Für den Aufbau der Datenbankverbindung ist beim Einsatz von Code First eine Connection-Factory verantwortlich. Dabei handelt es sich um eine Implementierung des Interface IDbConnectionFactory. Die standardmäßig herangezogene Connection-Factory sucht nach einer Datenbankverbindungszeichenfolge in der Applikationskonfigurationsdatei. Kann sie diese nicht finden, verwendet sie eine lokale SQL-Express-Instanz.
Um dieses Verhalten abzuändern, implementiert der Entwickler seine eigene Instanz von IDbConnectionFactory. Dabei stellt er auch die vom Interface vorgegebene Methode CreateConnection zur Verfügung, die den Namen einer Datenbank entgegennimmt und eine DbConnection zum Zugriff auf diese zurückgibt. Entity Framework übergibt jenen Namen, den das verwendete DbContext-Derivat an seinen Basiskonstruktor delegiert. Auf diese Weise kann der Entwickler z. B. bei einem mandantenfähigen System die Anwendung mit der Datenbank des aktuellen Mandanten verbinden.
Um eine benutzerdefinierte Connection-Factory zu registrieren, weist der Entwickler eine Instanz dieser entweder der statischen Eigenschaft Database.DefaultConnectionFactory zu oder übergibt sie innerhalb des zur Konfiguration verwendeten DbConfiguration-Derivats an die Methode SetDefaultConnectionFactory.
[ header = Seite 2: Pluralisierung anpassen ]
Pluralisierung anpassen
Beim Einsatz von Code First bildet Entity Framework standardmäßig Entitäten mit im Singular gehaltenen Namen auf Tabellen ab, deren Namen dem Plural der Entitätsbezeichnungen entsprechen. Dazu muss Entity Framework versuchen, den Plural zu bilden, was standardmäßig nur bei englischen Namen funktioniert. Mit Entity Framework 6 kann der Entwickler nun jene Komponente austauschen, die für die Pluralisierung verantwortlich ist. Dazu implementiert er das Interface IPluralizationService. Es gibt zwei Methoden vor: Pluralize nimmt ein Wort im Singular entgegen und gibt den dazugehörigen Plural zurück und Singularize nimmt ein Wort im Plural entgegen und liefert die dazu passende Singularform.
Unter Verwendung einer solchen Implementierung sowie eines freien Onlinewörterbuchs ist es dem Entwickler möglich, Entity Framework zur korrekten Handhabung nicht englischer Wörter zu bringen. Ein Beispiel solch einer Implementierung, die eine korrekte deutsche Pluralisierung ermöglicht, findet sich unter [1].
Zur Registrierung eines benutzerdefinierten Pluralization-Services verwendet der Entwickler die Methode SetPluralizationService innerhalb der zur Konfiguration genutzten DbConfiguration-Implementierung.
Eigene Migrationsanweisungen
Migrations bietet einige Anweisungen zum Generieren von SQL-Code für die Migration von Datenbanken. Wenn der Entwickler damit nicht auskommt, kann er sich eigene Anweisungen zurechtlegen. Dazu leitet er von MigrationOperation ab und verpasst seiner Implementierung Eigenschaften, die den jeweiligen Migrationsvorgang beschreiben (Listing 3). Im Zuge dessen überschreibt er auch die beiden geerbten Eigenschaften IsDestructiveChange sowie Inverse. Erstere liefert true, wenn im Zuge der Migrationsoperation Daten verloren gehen; letztere liefert eine andere MigrationOperation, um die von der implementierten Klasse angestoßene Aktion rückgängig zu machen, sofern möglich.
Listing 3 zeigt eine MigrationOperation, die die Änderung einer Spalte beschreibt, im Zuge derer sowohl der Name als auch der Datentyp geändert wird.
public class RenameColumnAndChangeTypeOperation : MigrationOperation { public string Table { get; set; } public string CurrentColumnName { get; set; } public string NewColumnName { get; set; } public string NewType { get; set; } public override bool IsDestructiveChange { [...] } public override MigrationOperation Inverse { [...] } }
Während MigrationOperation-Objekte die nötigen Migrationsschritte lediglich beschreiben, zieht Entity Framework Implementierungen von MigrationSqlGenerator zum Generieren von SQL-Anweisungen für Migrationen heran. Für SQL-Server-Datenbanken verwendet Entity Framework standardmäßig die Klasse SqlServerMigrationSqlGenerator. Zur Unterstützung eigener MigrationOperation-Implementierungen ist der Entwickler angehalten, einen eigenen MigrationSqlGenerator zu erstellen oder zumindest einen bestehenden zu erweitern. Listing 4 zeigt eine Klasse CustomSqlServerMigrationSqlGenerator, die den SqlServerMigrationSqlGenerator um eine Unterstützung für die in Listing 3 gezeigte RenameColumnAndChangeTypeOperation erweitert.
public class CustomSqlServerMigrationSqlGenerator: SqlServerMigrationSqlGenerator { protected override void Generate(MigrationOperation migrationOperation) { if (migrationOperation is EF6Samples.RenameColumnAndChangeTypeOperation) { var renameOp = migrationOperation as EF6Samples.RenameColumnAndChangeTypeOperation; Statement(string.Format("alter table {0} add {1} {2}", renameOp.Table, renameOp.NewColumnName, renameOp.NewType)); Statement(string.Format("update {0} set {1} = {2}", renameOp.Table, renameOp.NewColumnName, renameOp.CurrentColumnName)); Statement(string.Format("alter table {0} drop column {1}", renameOp.Table, renameOp.CurrentColumnName)); } } }
Neben der Unterstützung zusätzlicher MigrationOperationen kann der Entwickler benutzerdefinierte MigrationSqlGeneratoren heranziehen, um die Generierung von SQL-Anweisungen für bestehende MigrationOperationen anzupassen oder zu erweitern. Beispielsweise könnte eine eigene MigrationSqlGenerator-Implementierung für sämtliche Datenbanktabellen eine zusätzliche Historisierungstabelle anlegen und Änderungen, die an dieser Tabelle vorgenommen werden, auf die Historisierungstabelle anwenden [2].
Damit Migrations einen benutzerdefinierten MigrationSqlGenerator verwendet, muss der Entwickler diesen in der beim Aktivieren von Migrations generierten Konfigurationsklasse registrieren. Dazu ruft er innerhalb des Konstruktors dieser Konfigurationsklasse die geerbte Methode SetSqlGenerator auf. Als ersten Parameter übergibt er an diese Methode den so genannten Invariant-Namen jenes Datenbankproviders, für den der MigrationSqlGenerator entwickelt wurde (z. B. System.Data.SqlClient beim Einsatz des SQL Servers). An den zweiten Parameter übergibt er eine Instanz des MigrationSqlGenerators.
Fazit und Ausblick
Entwickler können Entity Framework 6 durch Austauschen interner Komponenten an die eigenen Bedürfnisse anpassen. Um eigene Komponenten zu implementieren, die bestehende ersetzen sollen, ist jeweils lediglich von einer Basisklasse abzuleiten oder ein Interface zu realisieren.
Neben den hier betrachteten Komponenten existieren noch weitere, die der Entwickler bei Bedarf austauschen kann. Häufig sind diese jedoch in erster Linie für Hersteller von Datenbankprovidern von Interesse. Ein Beispiel hierfür ist die Klasse DbSpatialServices, die Entity Framework zur Abstraktion der Geodatenunterstützung der einzelnen Datenbanksysteme heranzieht. Eine vollständige Liste der von Entity Framework verwendeten und durch den Entwickler austauschbaren Komponenten findet sich unter [3].