Wie es Ihnen gefällt

Entity Framework 6 an die eigenen Bedürfnisse anpassen
Kommentare

Entity Framework bietet einige Erweiterungspunkte, die dem Entwickler eine Anpassung an eigene Bedürfnisse erlauben. Damit kann er den O/R Mapper aus dem Hause Microsoft unter anderem veranlassen, Datenbanken auf benutzerdefinierte Weise zu initialisieren oder eine eigene Logik für die Pluralisierung von Klassennamen bereitzustellen.

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 Typparameter T steht hier für den Typ des jeweiligen Kontexts. Ein Beispiel für einen benutzerdefinierten Database Initializer findet sich in Listing 1. Es zeigt einen Initializer, der im Gegensatz zu MigrateDatabaseToLatestVersion nicht auf die neuste Version, sondern auf eine an den Konstruktor zu übergebende Version migriert. Diese Implementierung weist zwei Typparameter auf: TContext repräsentiert den Typ des jeweiligen Kontexts und TMigrationsConfiguration den Typ der Konfigurationsklasse, die im Zuge der Aktivierung von Migrations generiert wird. Den ersten Typparameter delegiert die betrachtete Implementierung an das Interface IDatabaseInitializer. Daneben schränkt sie diesen auf den Typ DbContext und dessen Subklassen ein. Den zweiten Typparameter schränkt sie hingegen auf den Typ DbMigrationsConfiguration ein. Daneben legt sie für diesen Typparameter fest, dass der hierdurch repräsentierte Typ einen parameterlosen Konstruktor aufzuweisen hat.

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].

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -