Benutzerdefinierte Reverse-Engineering-Vorlagen

Hinweis

Dieses Feature wurde in EF Core 7 hinzugefügt.

Während dem Reverse-Engineering ist Entity Framework Core bestrebt, ein Gerüst für guten, universell einsetzbaren Code zu erstellen, der in einer Vielzahl von App-Typen verwendet werden kann und allgemeine Codierungskonventionen für ein einheitliches Aussehen und ein vertrautes Gefühl verwendet. Manchmal sind jedoch spezialisierterer Code und alternative Codierungsstile wünschenswert. In diesem Artikel wird gezeigt, wie Sie den Gerüstcode mithilfe von T4-Textvorlagenanpassen.

Voraussetzungen

In diesem Artikel wird davon ausgegangen, dass Sie mit Reverse-Engineering in EF Core vertraut sind. Falls nicht, lesen Sie bitte diesen Artikel, bevor Sie fortfahren.

Hinzufügen der Standardvorlagen

Der erste Schritt zum Anpassen des Gerüstcodes besteht darin, Ihrem Projekt die Standardvorlagen hinzuzufügen. Die Standardvorlagen sind diejenigen, die von EF Core beim Reverse-Engineering intern verwendet werden. Sie bieten einen Startpunkt, um mit der Anpassung des Gerüstcodes zu beginnen.

Beginnen Sie mit der Installation des EF Core-Vorlagenpakets für dotnet new:

dotnet new install Microsoft.EntityFrameworkCore.Templates

Jetzt können Sie Ihrem Projekt die Standardvorlagen hinzufügen. Führen Sie dazu den folgenden Befehl aus Ihrem Projektverzeichnis aus.

dotnet new ef-templates

Dieser Befehl fügt Ihrem Projekt die folgenden Dateien hinzu.

  • CodeTemplates/
    • EFCore/
      • DbContext.t4
      • EntityType.t4

Die DbContext.t4-Vorlage wird verwendet, um ein Gerüst für eine DbContext-Klasse für die Datenbank zu erstellen, und die EntityType.t4-Vorlage wird zum Erstellen von Gerüsten für Klassen von Entitätstypen für jede Tabelle und Ansicht in der Datenbank verwendet.

Tipp

Die .t4-Erweiterung wird verwendet (anstelle von .tt), um zu verhindern, dass Visual Studio die Vorlagen transformiert. Die Vorlagen werden stattdessen von EF Core transformiert.

Einführung in T4

Lassen Sie uns die DbContext.t4-Vorlage öffnen und deren Inhalt prüfen. Diese Datei ist eine T4-Textvorlage. T4 ist eine Sprache zum Generieren von Text mithilfe von .NET. Der folgende Code dient nur zur Veranschaulichung. Er stellt nicht den vollständigen Inhalt der Datei dar.

Wichtig

T4-Textvorlagen – insbesondere solche, die Code generieren – können ohne Syntaxhervorhebung schwierig zu lesen sein. Suchen Sie falls notwendig nach einer Erweiterung für Ihren Code-Editor, der T4-Syntaxhervorhebungen ermöglicht.

<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#
    if (!string.IsNullOrEmpty(NamespaceHint))
    {
#>
namespace <#= NamespaceHint #>;

Die ersten Zeilen, die mit <#@ beginnen, werden als Anweisungen bezeichnet. Sie beeinflussen, wie die Vorlage transformiert wird. In der folgenden Tabelle werden alle verwendeten Anweisungen kurz beschrieben.

Anweisung Beschreibung
template Gibt hostSpecific="true" an, wodurch die Verwendung der Host-Eigenschaft innerhalb der Vorlage für den Zugriff auf EF Core-Dienste ermöglicht wird.
assembly Fügt Assemblyverweise hinzu, die zum Kompilieren der Vorlage erforderlich sind.
parameter Deklariert Parameter, die beim Transformieren der Vorlage von EF Core übergeben werden.
import Wie bei C# werden mithilfe von Anweisungen Namespaces in den Bereich für den Vorlagencode gebracht.

Der nächste Abschnitt von DbContext.t4 nach den Anweisungen wird als Steuerelementblock bezeichnet. Ein Standardsteuerelementblock beginnt mit <# und endet mit #>. Der darin enthaltene Code wird beim Transformieren der Vorlage ausgeführt. Eine Liste der in Steuerelementblöcken verfügbaren Eigenschaften und Methoden finden Sie in der TextTransformation-Klasse.

Alles außerhalb eines Steuerelementblocks wird direkt in die Vorlagenausgabe kopiert.

Ein Ausdruckssteuerelementblock beginnt mit <#=. Der darin enthaltene Code wird ausgewertet, und das Ergebnis wird der Vorlagenausgabe hinzugefügt. Diese sind ähnlich den interpolierten C#-Zeichenfolgeargumenten.

Eine ausführlichere und vollständigere Erläuterung der T4-Syntax finden Sie unter Schreiben einer T4-Textvorlage.

Anpassen der Entitätstypen

Sehen wir uns an, wie man eine Vorlage anpasst. Standardmäßig generiert EF Core den folgenden Code für Sammlungsnavigationseigenschaften.

public virtual ICollection<Album> Albums { get; } = new List<Album>();

Die Verwendung von List<T> ist ein guter Standard für die meisten Anwendungen. Wenn Sie jedoch ein XAML-basiertes Framework wie WPF, WinUI oder .NET MAUI verwenden, sollten Sie stattdessen ObservableCollection<T> verwenden, um die Datenbindung zu aktivieren.

Öffnen Sie die EntityType.t4-Vorlage, und finden Sie die Stelle, an der List<T> generiert wird. Dies sieht wie folgt aus:

    if (navigation.IsCollection)
    {
#>
    public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new List<<#= targetType #>>();
<#
    }

Ersetzen Sie die Liste durch ObservableCollection.

public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; } = new ObservableCollection<<#= targetType #>>();

Außerdem müssen wir dem Gerüstcode eine using-Anweisung hinzufügen. Die Verwendungsmöglichkeiten werden in einer Liste am oberen Rand der Vorlage angegeben. Fügen Sie System.Collections.ObjectModel zur Liste hinzu.

var usings = new List<string>
{
    "System",
    "System.Collections.Generic",
    "System.Collections.ObjectModel"
};

Testen Sie die Änderungen mithilfe der Reverse-Engineering-Befehle. Die Vorlagen in Ihrem Projekt werden automatisch von den Befehlen verwendet.

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer

Wenn Sie den Befehl zuvor ausgeführt haben, fügen Sie die --force-Option hinzu, um die vorhandenen Dateien zu überschreiben.

Wenn Sie alles richtig gemacht haben, sollten die Sammlungsnavigationseigenschaften nun ObservableCollection<T> verwenden.

public virtual ICollection<Album> Albums { get; } = new ObservableCollection<Album>();

Aktualisieren der Vorlagen

Wenn Sie Ihrem Projekt die Standardvorlagen hinzufügen, wird eine Kopie dieser Vorlagen basierend auf dieser Version von EF Core erstellt. Da in nachfolgenden Versionen von EF Core Fehler behoben und Features hinzugefügt werden, sind Ihre Vorlagen möglicherweise veraltet. Sie sollten die Änderungen überprüfen, die in den EF Core-Vorlagen vorgenommen wurden, und sie in Ihren angepassten Vorlagen zusammenführen.

Eine Möglichkeit, die an den EF Core-Vorlagen vorgenommenen Änderungen zu überprüfen, besteht darin, den Befehl „git“ zum Vergleichen zwischen Versionen zu verwenden. Der folgende Befehl klont das EF Core-Repository und generiert ein Diff dieser Dateien zwischen den Versionen 7.0.0 und 8.0.0.

git clone --no-checkout https://github.com/dotnet/efcore.git
cd efcore
git diff v7.0.0 v8.0.0 -- src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.tt

Eine weitere Möglichkeit zum Überprüfen der Änderungen besteht darin, die beiden Versionen von Microsoft.EntityFrameworkCore.Templates aus NuGet herunterzuladen, deren Inhalt zu extrahieren (Sie können die Dateierweiterungen in .zip ändern) und diese Dateien vergleichen.

Denken Sie vor dem Hinzufügen der Standardvorlagen zu einem neuen Projekt daran, auf das neueste EF Core-Vorlagenpaket zu aktualisieren.

dotnet new update

Erweiterte Verwendung

Ignorieren des Eingabemodells

Die Parameter Model und EntityType stellen einen möglichen Weg zur Zuordnung zur Datenbank dar. Sie können wählen, ob Sie Teile des Modells ignorieren oder ändern möchten. Beispielsweise sind die von uns bereitgestellten Navigationsnamen möglicherweise nicht ideal, und Sie können sie mit Ihren eigenen ersetzen, wenn Sie das Gerüst für den Code erstellen. Andere Dinge wie Einschränkungsnamen und Indexfilter werden nur von Migrationen verwendet und können im Modell problemlos weggelassen werden, wenn Sie nicht beabsichtigen, Migrationen mit dem Gerüstcode zu verwenden. Ebenso sollten Sie Sequenzen oder Standardeinschränkungen weglassen, wenn sie von Ihrer App nicht verwendet werden.

Stellen Sie beim Vornehmen erweiterter Änderungen wie diesen sicher, dass das resultierende Modell mit der Datenbank kompatibel bleibt. Das Überprüfen der von dbContext.Database.GenerateCreateScript() generierten SQL ist eine gute Möglichkeit, dies zu überprüfen.

Entitätskonfigurationsklassen

Bei großen Modellen kann die OnModelCreating-Methode der DbContext-Klasse unüberschaubar groß werden. Eine Möglichkeit, dies zu beheben, ist die Verwendung von IEntityTypeConfiguration<T>-Klassen. Weitere Informationen zu diesen Klassen finden Sie unter Erstellen und Konfigurieren eines Modells.

Zum Erstellen eines Gerüsts für diese Klassen können Sie eine dritte Vorlage mit dem Namen EntityTypeConfiguration.t4 verwenden. Wie die EntityType.t4-Vorlage wird sie für jeden Entitätstyp im Modell verwendet und verwendet den EntityType-Vorlagenparameter.

Erstellen eines Gerüsts für andere Dateitypen

Der Hauptzweck von Reverse-Engineering in EF Core besteht darin, ein Gerüst für DbContext und Entitätstypen zu erstellen. Es gibt jedoch nichts in den Tools, das erfordern würde, dass Sie tatsächlich Gerüstcode erstellen. Beispiel: Sie könnten stattdessen ein Gerüst für ein Entitätsbeziehungsdiagramm mit Mermaid erstellen.

<#@ output extension=".md" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ parameter name="Model" type="Microsoft.EntityFrameworkCore.Metadata.IModel" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
# <#= Options.ContextName #>

```mermaid
erDiagram
<#
    foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
    {
#>
    <#= entityType.Name #> {
    }
<#
        foreach (var foreignKey in entityType.GetForeignKeys())
        {
#>
    <#= entityType.Name #> <#= foreignKey.IsUnique ? "|" : "}" #>o--<#= foreignKey.IsRequired ? "|" : "o" #>| <#= foreignKey.PrincipalEntityType.Name #> : "<#= foreignKey.GetConstraintName() #>"
<#
        }

        foreach (var skipNavigation in entityType.GetSkipNavigations().Where(n => n.IsLeftNavigation()))
        {
#>
    <#= entityType.Name #> }o--o{ <#= skipNavigation.TargetEntityType.Name #> : <#= skipNavigation.JoinEntityType.Name #>
<#
        }
    }
#>
```