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:
Zum einen die Umbenennungsliste sowohl in DbContext.t4 als auch in EntityType.t4:
var entityNames = new Dictionary<string, string>
{
{ "Altetabelle", "NeueTabelle" },
{ "Altetabellezwei", "NeueTabelleZwei" },
};
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.