Sie sind hier: Weblog

Automatisch Historisierungs-Tabellen mit Entity Framework 6 und Migrations erstellen

Foto ,
03.12.2013 20:59:00

 

Entity Framework Migrations setzt eine Implementierung von MigrationSqlGenerator ein, um aus den Anweisungen in den einzelnen Migrationen SQL-Code für die jeweilige Ziel-Datenbank zu genieren. Die Aufgabe einer solchen Implementierung liegt darin, die einzelnen Anweisungen, welche durch Instanzen von MigrationOperation repräsentiert werden, in Instanzen von MigrationStatement, welche die konkreten SQL-Anweisungen repräsentieren, umzuwandeln.

Da die MigrationSqlGenerator-Instanzen, welche Entity Framework und Migrations standardmäßig verwenden, austauschbar sind, kann der Entwickler Unterstützung für benutzerdefinierte MigrationOperation-Implementierungen bereitstellen. Darüber hinaus bietet dieser Umstand auch die Möglichkeit, zusätzlichen SQL-Code für bestehende Migrations-Operationen zu generieren, zum Beispiel solche, welche neben den eigentlichen Tabellen auch Historisierungs-Tabellen erzeugen bzw. migrieren.

Das nachfolgende Beispiel zeigt, wie diese Aufgabe realisiert werden kann, indem es einen Decorator für MigrationSqlGenerator-Instanzen bereitstellt. Durch den Einsatz eines Decorators kann die hiermit bereitgestellte Logik zur Erweiterung von sämtlichen konkreten MigrationSqlGenerator-Implementierungen herangezogen werden.

public class MigrationSqlGeneratorDecorator : MigrationSqlGenerator
{
    protected MigrationSqlGenerator generator;

    public MigrationSqlGeneratorDecorator(MigrationSqlGenerator generator)
    {
        this.generator = generator;
    }
        

    public override IEnumerable<MigrationStatement> Generate(IEnumerable<System.Data.Entity.Migrations.Model.MigrationOperation> migrationOperations, string providerManifestToken)
    {
        // MigrationStatement für eigentliche Tabellen aus MigrationOperations generieren
        var statements = generator.Generate(migrationOperations, providerManifestToken);

        // Aus MigrationOperations für eigentliche Tabellen MigrationOperations für History-Tabellen ableiten
        var historyMigrationOperations = BuildHistoryOperations(migrationOperations);

        // MigrationStatement für History-Tabellen generieren
        var historyStatements = generator.Generate(historyMigrationOperations, providerManifestToken);
            
        // Die beiden Mengen mit MigrationStatements zusammenführen und retournieren
        var result = new List<MigrationStatement>();
        result.AddRange(statements);
        result.AddRange(historyStatements);
            return result;
    }
  
    private List<MigrationOperation> BuildHistoryOperations(IEnumerable<MigrationOperation> migrationOperations)
    {
        var historyMigrationOperations = new List<MigrationOperation>();

        foreach (var op in migrationOperations)
        {
            var createTableOp = op as CreateTableOperation;

            if (createTableOp != null)
            {
                BuildCreateTableOperation(createTableOp, historyMigrationOperations);
                    
            }

            var dropTableOp = op as DropTableOperation;

            if (dropTableOp != null)
            {
                historyMigrationOperations.Add(new DropTableOperation(dropTableOp.Name + "_History"));
            }

            var addColumnOp = op as AddColumnOperation;

            if (addColumnOp != null)
            {
                historyMigrationOperations.Add(new AddColumnOperation(addColumnOp.Table + "_History", addColumnOp.Column, addColumnOp.AnonymousArguments));
                    
            }

            var alterColumnOp = op as AlterColumnOperation;

            if (alterColumnOp != null)
            {
                historyMigrationOperations.Add(new AlterColumnOperation(alterColumnOp.Table, alterColumnOp.Column, alterColumnOp.IsDestructiveChange, alterColumnOp.AnonymousArguments));
            }

            var dropColumnOp = op as DropColumnOperation;

            if (dropColumnOp != null)
            {
                    
                historyMigrationOperations.Add(new DropColumnOperation(dropColumnOp.Table, dropColumnOp.Name, dropColumnOp.AnonymousArguments));
            }
                
                
            // TODO: Dieses Spielchen könnte hier für weitere Operationen wiederholt werden ...

        }
        return historyMigrationOperations;
    }
  
     
    private void BuildCreateTableOperation(CreateTableOperation createTableOp, List<MigrationOperation> historyMigrationOperations)
    {

        var tableName = createTableOp.Name + "_History";

        var createHistoryTableOp = new CreateTableOperation(tableName);
            
        var guidColumnModel = new ColumnModel(PrimitiveTypeKind.Guid);
        guidColumnModel.DefaultValueSql = "newid()";
        guidColumnModel.Name = "HistoryId";

        var dateColumnModel = new ColumnModel(PrimitiveTypeKind.DateTime);
        dateColumnModel.DefaultValueSql = "getdate()";
        dateColumnModel.Name = "DateOfModification";

        var actionColumnModel = new ColumnModel(PrimitiveTypeKind.String);
        actionColumnModel.Name = "Action";
        actionColumnModel.MaxLength = 10;

        createHistoryTableOp.Columns.Add(guidColumnModel);
        createHistoryTableOp.Columns.Add(dateColumnModel);
        createHistoryTableOp.Columns.Add(actionColumnModel);
                        
        foreach (var col in createTableOp.Columns)
        {
            createHistoryTableOp.Columns.Add(col);
        }

        createHistoryTableOp.PrimaryKey = new AddPrimaryKeyOperation();
        createHistoryTableOp.PrimaryKey.Columns.Add("HistoryId");

        historyMigrationOperations.Add(createHistoryTableOp);
    }


    public override string GenerateProcedureBody(ICollection<System.Data.Entity.Core.Common.CommandTrees.DbModificationCommandTree> commandTrees, string rowsAffectedParameter, string providerManifestToken)
    {
        return generator.GenerateProcedureBody(commandTrees, rowsAffectedParameter, providerManifestToken);
    }

}
Damit Migrations diesen Decorator verwendet, muss der Entwickler ihn innerhalb der Konfigurations-Klasse, die beim Aktivieren von Migrations generiert wird, registrieren. Dazu verwendet er die Methode SetSqlGenerator:
this.SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerMigrationSqlGenerator());