Entity Framework Core (EFCore) Reverse Engineering und property renaming / Eigenschaften umbenennen

Kategorie: c# | Development | Techblog

Entity Framework Core (EFCore) unterstützt das Umbenennen von Eigenschaften anhand von T4 Code-Templates. Wer bisher das Reverse Engineering mit EF Core Power Tools gemacht hat und von dessen Renaming-Feature gebrauch gemacht hat, wurde Anfang des Jahres 2024 von einer Warnung überrascht:

„Property renaming (experimental) is no longer available. See GitHub issue #2171.“

In dem GitHub-Issue wurde auf die T4-Code Templates hingewiesen. Wenn man sich damit nun voller Enttäuschung auseinandersetzt, wird eigentlich schnell klar, dass die T4-Code Templates mehr Möglichkeiten bieten, als das „stumpfe“ umbenennen von fixen Eigenschaftennamen, wie bisher.

Erik, der Autor der Extension „EF Core Power Tools“ hat mit Veröffentlichung des Updates ein Umbenennungs-Beispiel veröffentlicht: github.com

Zu sehen sind hier 2 Dateien:

  • DbContext.t4
  • EntityType.t4

Die Namen sind recht intuitiv – DbContext ist das Template für die generierten Context-Klassen, EntityType für die Entities.

Um diese im Projekt zu erstellen mit dem Terminal folgenden Befehl ausführen um die Code-Templates zu installieren: (Quelle: Microsoft.com)

dotnet new install Microsoft.EntityFrameworkCore.Templates

Anschließend im Terminal in das Verzeichnis des Projekts gehen und die Templates hinzufügen:

dotnet new ef-templates

Nun wurden im Projekt unter dem Ordner „CodeTemplates/EFCore“ die entsprechenden Templates hinzugefügt.

Um in einer Entität nun z.B. Eigenschaften systematisch umbenennen zu können müssen folgende Zeilen der EntityType.t4 hinzugefügt werden:

...
// Template version: 703 - please do NOT remove this line
if (EntityType.IsSimpleManyToManyJoinEntityType())
{
    // Don't scaffold these
    return "";
}

// Property renaming list { BEGIN
var propertyNames = new List<(string EntityName, string PropertyName, string NewPropertyName)>
{
    ("EntityNameA", "PropertyAName", "PropertyANewName"),
    ("EntityNameB", "PropertyBName", "PropertyBNewName"),
};
// Property renaming list END }

var services = (IServiceProvider)Host;
var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();
...

Zur Orientierung sind ein Paar Zeilen davor und danach mit eingefügt. Die eigentliche Umbenennung findet dann weiter unten satt:

...
public partial class <#= EntityType.Name #>
{
    <# var firstProperty = true; foreach (var property in EntityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1))
...

        var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !property.ClrType.IsValueType;
        var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !property.ClrType.IsValueType;
        var renameTuple = propertyNames.SingleOrDefault(
            t => (
                t.EntityName == EntityType.Name || (
                    entityNames.ContainsKey(EntityType.Name) && t.EntityName == entityNames[EntityType.Name]
                )
            ) && t.PropertyName == property.Name);
        var propertyName = renameTuple.NewPropertyName ?? property.Name;
#>
    public <#= code.Reference(property.ClrType) #><#= needsNullable ? "?" : "" #> <#=propertyName #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#

Halte ausschau nach der Schleife „forreach (var property in EntityType.GetPropeties()….“. Nicht weit unterhalb dieser Zeilen, innerhalb der Schleife werden die EIgenschaften erstellt.

Wie man Navigations-Eigenschaften umbennent hat Erik in seinem Beispiel gezeigt (siehe Oben). Beachte dabei die Änderung auch in dem DbContext.t4-Template.

Allerdings war das Beispiel für eine recht komplexe Datenbank eines Projektes nicht ausreichend, da ich zum einen einige Tabellen / Entitäten in Gänze umbennen wollte und hierbei Relationen gab bei denen die FluentApi der generischen „HasForeignKey()“-Methode ein Typenargument voranstellt. Die FluentApi bekommt allerdings nichts von der Umbenennung mit und der Code wird nicht vom T4-Template beeinflusst, sondern via „code.Fragement()“ aus dem FluentApiCall-Objekt erstellt.

Auf der Basis des Templates von Erik möchte ich hier folgende Ergänzungen beisteuern, welche sowohl in der DbContext.t4, als auch in der EntityType.t4 anwendung finden:

var entityNames = new Dictionary<string, string>
{
    { "Altetabelle", "NeueTabelle" },
    { "Altetabellezwei", "NeueTabelleZwei" },
};

...

<#
    foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
    {
        var entityName = entityNames.ContainsKey(entityType.Name) ? entityNames[entityType.Name] : entityType.Name;
        var entityDbSetName = entityType.GetDbSetName();
        var entityDbSetNameNew = entityNames.ContainsKey(entityDbSetName) ? entityNames[entityDbSetName] : entityDbSetName;
#>
    public virtual DbSet<<#= entityName #>> <#= entityDbSetNameNew #> { get; set; }

...
...

        var anyEntityTypeConfiguration = false;
        var entityName = entityNames.ContainsKey(entityType.Name) ? entityNames[entityType.Name] : entityType.Name;
#>
        modelBuilder.Entity<<#= entityName #>>(entity =>
        {
<#
        var key = entityType.FindPrimaryKey();

...
...
        // Renaming foreign keys and relation-properties
        var renameTuple = propertyNames.SingleOrDefault(t =>
            t.EntityTypeName == entityName &&
            (t.DependentToPrincipalName == foreignKey.DependentToPrincipal.Name 
                || (entityNames.ContainsKey(foreignKey.DependentToPrincipal.Name) 
                    && t.DependentToPrincipalName == entityNames[foreignKey.DependentToPrincipal.Name])
            ) && (t.PrincipalToDependentName == foreignKey.PrincipalToDependent.Name 
                || (entityNames.ContainsKey(foreignKey.PrincipalToDependent.Name) 
                    && t.PrincipalToDependentName == entityNames[foreignKey.PrincipalToDependent.Name])));

var dependentToPrincipalName = renameTuple.NewDependentToPrincipalName ?? foreignKey.DependentToPrincipal.Name;
var principalToDependentName = renameTuple.NewPrincipalToDependentName ?? foreignKey.PrincipalToDependent.Name;

            // Renaming property names (type) in FluentApi
            for (int i = 0; i < foreignKeyFluentApiCalls.TypeArguments.Count; i++)
            {
				var type = foreignKeyFluentApiCalls.TypeArguments[i];
				if (entityNames.ContainsKey(type))
					foreignKeyFluentApiCalls.TypeArguments[i] = entityNames[type];
            }
#>
            entity.HasOne(d => d.<#= dependentToPrincipalName #>).<#= foreignKey.IsUnique ? "WithOne" : "WithMany" #>(<#= foreignKey.PrincipalToDependent != null ? $"p => p.{principalToDependentName}" : "" #>)<#= code.Fragment(foreignKeyFluentApiCalls, indent: 4) #>;

...

    var entityName = entityNames.ContainsKey(EntityType.Name) ? entityNames[EntityType.Name] : EntityType.Name;
    var services = (IServiceProvider)Host;
    var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
    var code = services.GetRequiredService<ICSharpHelper>();

...

#>
public partial class <#= entityName #>
{
<#

...

Wenn nun das Scaffolding ausgeführt wird, werden die Context-Klassen und Entities nach den Templates erstellt.

Der Perfekte Kaffee

Der Perfekte Kaffee

Ähnlich wie Menschen einzigartig sind, so ist sind es auch die Sinne und der Geschmack eines jeden. Manche mögen ihren Kaffee mild und fruchtig,...