Partager via


Modèles d'ingénierie inverse personnalisés

Remarque

Cette fonctionnalité a été ajoutée dans EF Core 7.

Pendant que l’ingénierie à rebours, Entity Framework Core s’efforce de créer un code à usage général approprié qui peut être utilisé dans divers types d’applications et utilise conventions de codage courantes pour une apparence cohérente et une sensation familière. Toutefois, parfois, du code plus spécialisé et d’autres styles de codage sont souhaitables. Cet article montre comment personnaliser le code généré automatiquement à l’aide de modèles de texte T4.

Prérequis

Cet article suppose que vous connaissez ingénierie à rebours dans EF Core. Si ce n’est pas le cas, veuillez consulter cet article avant de continuer.

Ajout des modèles par défaut

La première étape de personnalisation du code généré automatiquement consiste à ajouter les modèles par défaut à votre projet. Les modèles par défaut sont ceux utilisés en interne par EF Core lors de l’ingénierie à rebours. Ils fournissent un point de départ pour commencer à personnaliser le code généré automatiquement.

Commencez par installer le package de modèle EF Core pour dotnet new :

dotnet new install Microsoft.EntityFrameworkCore.Templates

Vous pouvez maintenant ajouter les modèles par défaut à votre projet. Pour ce faire, exécutez la commande suivante à partir de votre répertoire de projet.

dotnet new ef-templates

Cette commande ajoute les fichiers suivants à votre projet.

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

Le modèle DbContext.t4 est utilisé pour générer une structure d’une classe DbContext pour la base de données, et le modèle EntityType.t4 est utilisé pour générer des modèles de classes de type d’entité pour chaque table et vue dans la base de données.

Conseil

L’extension .t4 est utilisée (au lieu de .tt) pour empêcher Visual Studio de transformer les modèles. Les modèles seront transformés par EF Core à la place.

Présentation de T4

Nous allons ouvrir le modèle DbContext.t4 et inspecter son contenu. Ce fichier est un modèle de texte T4. T4 est un langage permettant de générer du texte à l’aide de .NET. Le code suivant est à des fins d’illustration uniquement, il ne représente pas le contenu complet du fichier.

Important

Les modèles de texte T4, en particulier ceux qui génèrent du code, peuvent être difficiles à lire sans mise en surbrillance de syntaxe. Si nécessaire, recherchez une extension à votre éditeur de code qui active la mise en surbrillance de la syntaxe T4.

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

Les premières lignes commençant par <#@ sont appelées directives. Ils affectent la façon dont le modèle est transformé. Le tableau suivant décrit brièvement chaque type de directive utilisée.

Directive Description
template Spécifie hostSpecific="true" qui permet d’utiliser la propriété Host à l’intérieur du modèle pour accéder aux services EF Core.
assembly Ajoute des références d’assembly requises pour compiler le modèle.
parameter Déclare les paramètres qui seront transmis par EF Core lors de la transformation du modèle.
import Comme C# à l’aide de directives, place les espaces de noms dans l’étendue du code du modèle.

Après les directives, la section suivante de DbContext.t4 est appelée bloc de contrôle. Un bloc de contrôle standard commence par <# et se termine par #>. Le code à l’intérieur de celui-ci est exécuté lors de la transformation du modèle. Pour obtenir la liste des propriétés et méthodes disponibles à l’intérieur des blocs de contrôle, consultez la classe TextTransformation .

Tout ce qui se trouve en dehors d’un bloc de contrôle est copié directement dans la sortie du modèle.

Un bloc de contrôle d’expression commence par <#=. Le code à l’intérieur de celui-ci sera évalué et le résultat sera ajouté à la sortie du modèle. Ces arguments sont similaires aux arguments de chaîne interpolée C#.

Pour obtenir une explication plus détaillée et complète de la syntaxe T4, consultez Écriture d’un modèle de texte T4.

Personnaliser les types d’entités

Examinons ce qu’il est semblable à la personnalisation d’un modèle. Par défaut, EF Core génère le code suivant pour les propriétés de navigation de collection.

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

L’utilisation de List<T> est une bonne valeur par défaut pour la plupart des applications. Toutefois, si vous utilisez une infrastructure XAML telle que WPF, WinUI ou .NET MAUI, vous souhaitez souvent utiliser ObservableCollection<T> à la place pour activer la liaison de données.

Ouvrez le modèle EntityType.t4 et recherchez où il génère List<T>. Elle se présente comme suit :

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

Remplacez List par ObservableCollection.

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

Nous devons également ajouter une directive using au code généré. Les utilisations sont spécifiées dans une liste située en haut du modèle. Ajoutez System.Collections.ObjectModel à la liste.

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

Testez les modifications à l’aide des commandes d’ingénierie à rebours. Les modèles à l’intérieur de votre projet sont utilisés automatiquement par les commandes.

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

Si vous avez exécuté la commande précédemment, ajoutez l’option --force pour remplacer les fichiers existants.

Si vous avez fait tout correctement, les propriétés de navigation de collection doivent maintenant utiliser ObservableCollection<T>.

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

Mise à jour des modèles

Lorsque vous ajoutez les modèles par défaut à votre projet, il crée une copie basée sur cette version d’EF Core. À mesure que les bogues sont corrigés et que les fonctionnalités sont ajoutées dans les versions suivantes d’EF Core, vos modèles peuvent devenir obsolètes. Vous devez passer en revue les modifications apportées dans les modèles EF Core et les fusionner dans vos modèles personnalisés.

Une façon de passer en revue les modifications apportées aux modèles EF Core consiste à utiliser git pour les comparer entre les versions. La commande suivante clone le référentiel EF Core et génère un écart entre les versions 7.0.0 et 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

Une autre façon de passer en revue les modifications consiste à télécharger les deux versions de Microsoft.EntityFrameworkCore.Templates à partir de NuGet, extraire leur contenu (vous pouvez modifier les extensions de fichier en .zip) et comparer ces fichiers.

Avant d’ajouter les modèles par défaut à un nouveau projet, n’oubliez pas de mettre à jour le dernier package de modèle EF Core.

dotnet new update

Utilisation avancée

Ignorer le modèle d’entrée

Les paramètres Model et EntityType représentent un moyen possible de mapper à la base de données. Vous pouvez choisir d’ignorer ou de modifier des parties du modèle. Par exemple, les noms de navigation que nous fournissons peuvent ne pas être idéaux et vous pouvez les remplacer par vos propres lorsque vous créez une structure du code. D’autres éléments tels que les noms de contraintes et les filtres d’index sont utilisés uniquement par Migrations et peuvent être omis en toute sécurité à partir du modèle si vous n’avez pas l’intention d’utiliser migrations avec le code généré. De même, vous pouvez omettre des séquences ou des contraintes par défaut si elles ne sont pas utilisées par votre application.

Lorsque vous apportez des modifications avancées comme celle-ci, assurez-vous que le modèle résultant reste compatible avec la base de données. L’examen du code SQL généré par dbContext.Database.GenerateCreateScript() est un bon moyen de le valider.

Classes de configuration de test

Pour les modèles volumineux, la méthode OnModelCreating de la classe DbContext peut devenir volumineuse et difficilement gérable. L’une des façons d’y remédier consiste à utiliser des classes IEntityTypeConfiguration<T> . Pour plus d’informations sur ces classes, consultez Création et configuration d’un modèle.

Pour générer une structure de ces classes, vous pouvez utiliser un troisième modèle appelé EntityTypeConfiguration.t4. Comme le modèle EntityType.t4, il est utilisé pour chaque type d’entité dans le modèle et utilise le paramètre de modèle EntityType.

Génération automatique d’autres types de fichiers

L’objectif principal de l’ingénierie à rebours dans EF Core est de générer une structure de types DbContext et d’entité. Toutefois, il n’y a rien dans les outils qui vous obligent à générer du code automatiquement. Par exemple, vous pouvez générer au lieu de cela un diagramme de relation d’entité à l’aide de Mermaid.

<#@ 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 #>
<#
        }
    }
#>
```