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.
Eigenschaften umbenennen
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.
Ändern von Navigations-Eigenschaften oder Relationen
Wie man Navigations-Eigenschaften umbennent hat Erik in seinem Beispiel gezeigt (siehe Oben). Beachte dabei die Änderung auch in dem DbContext.t4-Template.
Ändern von ganzen Entitäten bzw. Tabellennamen
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" }, };
Zum einen die Umbenennungsliste sowohl in DbContext.t4 als auch in EntityType.t4:
Implementierung der Umbennenungslogik für die FluentApi in der DbContext.t4:
... <# 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) #>;
Nun die Ergänzungen in der EntityType.t4:
... 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.