Génération de modèles automatique (ingénierie à rebours)

L’ingénierie à rebours est le processus de génération de modèles automatiques de classes de type d’entité et d’une classe DbContext basée sur un schéma de base de données. Elle peut être effectuée à l’aide de la commande Scaffold-DbContext des outils de la console de gestionnaire de package (PMC) EF Core ou de la commande dotnet ef dbcontext scaffold des outils de l’interface de ligne de commande (CLI) .NET.

Remarque

La génération de modèles automatique d’un DbContext et des types d’entités documentés ici est distincte de la génération de modèles automatique des contrôleurs dans ASP.NET Core à l’aide de Visual Studio, ce qui n’est pas documenté ici.

Conseil

Si vous utilisez Visual Studio, essayez l’extension de communauté EF Core Power Tools. Ces outils fournissent un outil graphique qui s’appuie sur les outils de ligne de commande EF Core et offre des options de flux de travail et de personnalisation supplémentaires.

Prérequis

  • Avant de générer des modèles, vous devez installer soit les outils PMC, qui fonctionnent uniquement sur Visual Studio, soit les outils de l’interface CLI .NET, qui fonctionnent sur toutes les plateformes prises en charge par .NET.
  • Installez le package NuGet pour Microsoft.EntityFrameworkCore.Design dans le projet dans lequel vous effectuez la génération de modèles automatique.
  • Installez le package NuGet pour le fournisseur de base de données qui cible le schéma de base de données à partir duquel vous souhaitez générer automatiquement des modèles.

Arguments obligatoires

Les commandes PMC et CLI .NET ont deux arguments requis : utiliser la chaîne de connexion à la base de données et le fournisseur de base de données EF Core.

Connection string

Le premier argument de la commande est une chaîne de connexion à la base de données. Les outils utilisent cette chaîne de connexion pour lire le schéma de base de données.

La méthode de citation et d’échappement pour la chaîne de connexion dépend de l’interpréteur de commandes que vous utilisez pour exécuter la commande. Pour plus d’informations, reportez-vous à la documentation de votre interpréteur de commandes. Par exemple, PowerShell vous oblige à effectuer l’échappement du caractère $, mais pas de \.

L’exemple suivant génère des types d’entités et un DbContext à partir de la base de données Chinook située sur l’instance LocalDB de SQL Server de la machine, utilisant le fournisseur de base de données Microsoft.EntityFrameworkCore.SqlServer.

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

Secrets d’utilisateur pour les chaînes de connexion

Si vous avez une application .NET qui utilise le modèle d’hébergement et le système de configuration, comme un projet ASP.NET Core, vous pouvez utiliser la syntaxe Name=<connection-string> pour lire la chaîne de connexion à partir de la configuration.

Par exemple, considérez une application ASP.NET Core avec le fichier de configuration suivant :

{
  "ConnectionStrings": {
    "Chinook": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Chinook"
  }
}

Cette chaîne de connexion dans le fichier config peut être utilisée pour générer automatiquement des modèles à partir d’une base de données à l’aide des éléments suivants :

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

Toutefois, le stockage de chaînes de connexion dans les fichiers de configuration n’est pas une bonne idée, car il est trop facile de les exposer accidentellement, par exemple en envoyant (push) au contrôle de code source. Au lieu de cela, les chaînes de connexion doivent être stockées de manière sécurisée, en utilisant par exemple Azure Key Vault ou, lorsque vous travaillez localement, l’outil gestionnaire de secrets, alias « secret d’utilisateur ».

Par exemple, pour utiliser les secrets d’utilisateur, commencez par supprimer la chaîne de connexion de votre fichier de configuration ASP.NET Core. Ensuite, initialisez les secrets d’utilisateur en exécutant la commande suivante dans le même répertoire que le projet ASP.NET Core :

dotnet user-secrets init

Cette commande configure le stockage sur votre ordinateur séparément de votre code source et ajoute une clé pour ce stockage au projet.

Ensuite, stockez la chaîne de connexion dans les secrets d’utilisateur. Par exemple :

dotnet user-secrets set ConnectionStrings:Chinook "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook"

Maintenant, la même commande qui utilisait auparavant la chaîne de connexion nommée à partir du fichier config utilise plutôt la chaîne de connexion stockée dans les secrets d’utilisateur. Par exemple :

dotnet ef dbcontext scaffold "Name=ConnectionStrings:Chinook" Microsoft.EntityFrameworkCore.SqlServer

Chaînes de connexion dans le code généré automatiquement

Par défaut, le générateur de modèles automatique inclut la chaîne de connexion dans le code généré automatiquement, mais avec un avertissement. Par exemple :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
    => optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Database=AllTogetherNow");

Cela permet au code généré de ne pas se planter lors de la première utilisation, ce qui serait une expérience d’apprentissage très médiocre. Toutefois, comme l’indique l’avertissement, les chaînes de connexion ne doivent pas exister dans le code de production. Consultez Durée de vie, configuration et initialisation de DbContext pour connaître les différentes façons dont les chaînes de connexion peuvent être gérées.

Conseil

L’option -NoOnConfiguring (PMC Visual Studio) ou --no-onconfiguring (CLI .NET) peut être passée pour supprimer la création de la méthode OnConfiguring contenant la chaîne de connexion.

Nom du fournisseur

Le deuxième argument est le nom du fournisseur. Le nom du fournisseur est généralement identique au nom du package NuGet du fournisseur. Par exemple, pour SQL Server ou Azure SQL, utilisez Microsoft.EntityFrameworkCore.SqlServer.

Options de ligne de commande

Le processus de génération de modèles automatique peut être contrôlé par différentes options de ligne de commande.

Spécification des tables et des vues

Par défaut, toutes les tables et les vues du schéma de base de données sont générées automatiquement en types d'entités. Vous pouvez limiter les tables et les vues qui sont générées automatiquement en spécifiant des schémas et des tables.

L’argument -Schemas (PMC Visual Studio) ou --schema (CLI .NET) spécifie les schémas de tables et de vues pour lesquels les types d’entités seront générés. Si cet argument est omis, alors tous les schémas sont inclus. Si cette option est utilisée, alors toutes les tables et les vues dans les schémas seront incluses dans le modèle, même si elles ne sont pas explicitement incluses à l’aide de -Tables ou de --table.

L’argument -Tables (PMC Visual Studio) ou --table (CLI .NET) spécifie les tables et les vues pour lesquels les types d’entités seront générés. Vous pouvez inclure des tables ou des vues dans un schéma spécifique à l’aide du format « schema.table » ou « schema.view ». Si cette option est omise, alors toutes les tables et les vues sont incluses. |

Par exemple, pour générer automatiquement uniquement les tables Artists et Albums :

dotnet ef dbcontext scaffold ... --table Artist --table Album

Pour générer automatiquement toutes les tables et les vues à partir des schémas Customer et Contractor :

dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor

Par exemple, pour générer automatiquement la table Purchases à partir du schéma Customer, ainsi que les tables Accounts et Contracts à partir du schéma Contractor :

dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts

Conservation des noms de base de données

Les noms de tables et de colonnes sont corrigés pour mieux correspondre aux conventions d’affectation de noms .NET pour les types et les propriétés par défaut. La spécification de -UseDatabaseNames (PMC Visual Studio) ou de --use-database-names (CLI .NET) désactive ce comportement pour conserver autant que possible les noms de bases de données d’origine. Les identificateurs .NET non valides sont quand même corrigés et les noms synthétisés comme les propriétés de navigation restent conformes aux conventions d’affectation de noms .NET.

Par exemple, considérez les tables suivantes :

CREATE TABLE [BLOGS] (
    [ID] int NOT NULL IDENTITY,
    [Blog_Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([ID]));

CREATE TABLE [posts] (
    [id] int NOT NULL IDENTITY,
    [postTitle] nvarchar(max) NOT NULL,
    [post content] nvarchar(max) NOT NULL,
    [1 PublishedON] datetime2 NOT NULL,
    [2 DeletedON] datetime2 NULL,
    [BlogID] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogID]) REFERENCES [Blogs] ([ID]) ON DELETE CASCADE);

Par défaut, les types d’entités suivants sont générés automatiquement à partir de ces tables :

public partial class Blog
{
    public int Id { get; set; }
    public string BlogName { get; set; } = null!;
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

public partial class Post
{
    public int Id { get; set; }
    public string PostTitle { get; set; } = null!;
    public string PostContent { get; set; } = null!;
    public DateTime _1PublishedOn { get; set; }
    public DateTime? _2DeletedOn { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; } = null!;
    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

Toutefois, l’utilisation de -UseDatabaseNames ou de --use-database-names entraîne les types d’entités suivants :

public partial class BLOG
{
    public int ID { get; set; }
    public string Blog_Name { get; set; } = null!;
    public virtual ICollection<post> posts { get; set; } = new List<post>();
}

public partial class post
{
    public int id { get; set; }
    public string postTitle { get; set; } = null!;
    public string post_content { get; set; } = null!;
    public DateTime _1_PublishedON { get; set; }
    public DateTime? _2_DeletedON { get; set; }
    public int BlogID { get; set; }
    public virtual BLOG Blog { get; set; } = null!;
}

Utiliser des attributs de mappage (alias Annotations de données)

Les types d’entités sont configurés à l’aide de l’API ModelBuilder dans OnModelCreating par défaut. Spécifiez -DataAnnotations (PMC) ou --data-annotations (CLI .NET Core) pour utiliser à la place des attributs de mappage lorsque cela est possible.

Par exemple, l’utilisation de l’API Fluent génère automatiquement les modèles suivants :

entity.Property(e => e.Title)
    .IsRequired()
    .HasMaxLength(160);

Tandis que l’utilisation d’annotations de données génère automatiquement les modèles suivants :

[Required]
[StringLength(160)]
public string Title { get; set; }

Conseil

Certains aspects du modèle ne peuvent pas être configurés à l’aide d’attributs de mappage. Le générateur de modèles automatique utilise toujours l’API de génération de modèles pour gérer ces cas.

Nom DbContext

Par défaut, le nom de la classe de DbContext généré automatiquement est le nom de la base de données avec le suffixe Context ajouté. Pour en spécifier un autre, utilisez -Context dans PMC et --context dans l’interface CLI .NET Core.

Répertoires et espaces de noms cibles

Les classes d’entité et une classe DbContext sont générées automatiquement dans le répertoire racine du projet et utilisent l’espace de noms par défaut du projet.

Vous pouvez spécifier le répertoire dans lequel les classes sont générées automatiquement à l’aide de --output-dir, et vous pouvez utiliser --context-dir pour générer automatiquement le modèle de la classe DbContext dans un répertoire distinct de celui des classes de type d’entité :

dotnet ef dbcontext scaffold ... --context-dir Data --output-dir Models

Par défaut, l’espace de noms est l’espace de noms racine plus les noms des sous-répertoires sous le répertoire racine du projet. Toutefois, vous pouvez remplacer l’espace de noms pour toutes les classes de sortie à l’aide de --namespace. Vous pouvez également remplacer l’espace de noms pour la classe DbContext à l’aide de --context-namespace :

dotnet ef dbcontext scaffold ... --namespace Your.Namespace --context-namespace Your.DbContext.Namespace

Code généré automatiquement

Le résultat de la génération de modèles automatique à partir d’une base de données existante est le suivant :

  • Un fichier contenant une classe qui hérite de DbContext
  • Un fichier pour chaque type d’entité

Conseil

À compter d’EF7, vous pouvez également utiliser des modèles de texte T4 pour personnaliser le code généré. Pour plus d’informations, consultez Modèles d’ingénierie à rebours personnalisés.

Types références pouvant accepter la valeur Null C#

Le générateur automatique peut créer un modèle EF et des types d’entités qui utilisent des types référence pouvant accepter la valeur Null C# (NRT). L’utilisation de NRT est automatiquement générée lorsque la prise en charge de NRT est activée dans le projet C# dans lequel le code est généré automatiquement.

Par exemple, la table Tags suivante contient des colonnes de chaîne pouvant accepter ou non la valeur Null :

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

Cela génère des propriétés de chaîne nullable et non-nullable correspondantes dans la classe générée :

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

De même, la table Posts suivante contient une relation requise avec la table Blogs :

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]));

Cela entraîne la génération de modèles automatique de relation non-nullable (obligatoire) entre les blogs :

public partial class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;

    public virtual ICollection<Post> Posts { get; set; }
}

Et les publications :

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

Relations plusieurs-à-plusieurs

Le processus de génération de modèles automatique détecte les tables de jointure simples et génère automatiquement un mappage plusieurs-à-plusieurs pour elles. Par exemple, considérez les tables pour Posts et Tags, et une table de jointure PostTag les connectant :

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] int NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE);

Lorsque le modèle est généré, cela entraîne une classe pour Post :

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

Et une classe pour Tag :

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

Mais aucune classe pour la table PostTag. Au lieu de cela, la configuration d’une relation plusieurs-à-plusieurs est générée automatiquement :

entity.HasMany(d => d.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        l => l.HasOne<Tag>().WithMany().HasForeignKey("PostsId"),
        r => r.HasOne<Post>().WithMany().HasForeignKey("TagsId"),
        j =>
            {
                j.HasKey("PostsId", "TagsId");
                j.ToTable("PostTag");
                j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
            });

Autres langages de programmation

Les packages EF Core publiés par le code C# généré de Microsoft. Toutefois, le système de génération de modèles automatique sous-jacent prend en charge un modèle de plug-in pour la génération de modèles de génération automatique vers d’autres langages. Ce modèle de plug-in est utilisé par différents projets exécutés par la communauté, par exemple :

Personnalisation du code

À compter d’EF7, l’une des meilleures façons de personnaliser le code généré consiste à personnaliser les modèles T4 utilisés pour le générer.

Le code peut également être modifié après sa génération, mais la meilleure façon de procéder dépend de votre intention de réexécuter le processus de génération de modèles automatique lorsque le modèle de base de données change.

Génération de modèles automatique une seule fois

Avec cette approche, le code généré automatiquement fournit à l’avenir un point de départ pour le mappage basé sur le code. Toutes les modifications apportées au code généré peuvent être effectuées comme vous le souhaitez. Il devient du code normal comme n’importe quel autre code dans votre projet.

La synchronisation de la base de données et du modèle EF peut être effectuée de deux manières :

  • Passez à l’utilisation de migrations de base de données EF Core et utilisez les types d’entités et la configuration du modèle EF comme source de vérité, en utilisant des migrations pour piloter le schéma.
  • Mettez à jour manuellement les types d’entités et la configuration EF lorsque la base de données change. Par exemple, si une nouvelle colonne est ajoutée à une table, ajoutez une propriété pour la colonne au type d’entité mappé et ajoutez toute configuration nécessaire à l’aide d’attributs de mappage et/ou de code dans OnModelCreating. Cela est relativement facile, avec comme seul véritable défi d’être un processus pour s’assurer que les modifications de base de données sont enregistrées ou détectées d’une certaine manière afin que le(s) développeur(s) responsable(s) du code puisse(nt) réagir.

Génération de modèles automatique répétée

Une autre approche de la génération de modèles automatique consiste à regénérer automatiquement des modèles chaque fois que la base de données change. Cela remplacera tout code généré précédemment, ce qui signifie que les modifications apportées aux types d’entités ou à la configuration EF dans ce code seront perdues.

[ASTUCE] Par défaut, les commandes EF ne remplacent aucun code existant pour se protéger contre la perte accidentelle de code. L’argument -Force (PMC Visual Studio) ou --force (CLI .NET) peut être utilisé pour forcer le remplacement des fichiers existants.

Étant donné que le code généré automatiquement sera remplacé, il est préférable de ne pas le modifier directement, mais plutôt de s’appuyer sur des méthodes et classes partielles, et les mécanismes dans EF Core qui permettent la substitution de la configuration. Plus précisément :

  • La classe DbContext et les classes d’entité sont générées comme partielles. Cela permet d’introduire des membres et du code supplémentaires dans un fichier distinct qui ne sera pas substitué lors de l’exécution de la génération de modèles automatique.
  • La classe DbContext contient une méthode partielle appelée OnModelCreatingPartial. Une implémentation de cette méthode peut être ajoutée à la classe partielle pour le DbContext. Elle sera ensuite appelée, après que OnModelCreating soit appelé.
  • La configuration du modèle effectuée à l’aide des API ModelBuilder remplace toute configuration effectuée par des conventions ou des attributs de mappage, ainsi que la configuration antérieure effectuée sur le générateur de modèles. Cela signifie que le code dans OnModelCreatingPartial peut être utilisé pour remplacer la configuration générée par le processus de génération de modèles automatique, sans avoir à supprimer cette configuration.

Enfin, n’oubliez pas que à partir d’EF7, les modèles T4 utilisés pour générer du code peuvent être personnalisés. Il s’agit souvent d’une approche plus efficace que la génération de modèles automatique avec les valeurs par défaut, puis la modification avec des classes partielles et/ou des méthodes.

Fonctionnement

L’ingénierie à rebours commence par lire le schéma de base de données. Elle lit les informations sur les tables, les colonnes, les contraintes et les index.

Ensuite, elle utilise les informations de schéma pour créer un modèle EF Core. Les tables sont utilisées pour créer des types d’entités ; les colonnes sont utilisées pour créer des propriétés et les clés étrangères sont utilisées pour créer des relations.

Enfin, le modèle est utilisé pour générer du code. Les classes de type d’entité correspondantes, l’API Fluent et les annotations de données sont générées automatiquement pour recréer le même modèle à partir de votre application.

Limites

  • Tout ce qui concerne un modèle ne peut pas être représenté à l’aide d’un schéma de base de données. Par exemple, les informations sur les hiérarchies d’héritage, les types détenus et le fractionnement de table ne sont pas présentes dans le schéma de base de données. Ainsi, ces constructions ne feront jamais l’objet d’une génération automatique.
  • De plus, certains types de colonnes peuvent ne pas être pris en charge par le fournisseur EF Core. Ces colonnes ne seront pas incluses dans le modèle.
  • Vous pouvez définir des jetons d’accès concurrentiel dans un modèle EF Core pour empêcher deux utilisateurs de mettre à jour la même entité en même temps. Certaines bases de données ont un type spécial pour représenter ce type de colonne (par exemple, rowversion dans SQL Server) auquel cas nous pouvons appliquer l’ingénierie à rebours sur ces informations. Toutefois, les autres jetons d’accès concurrentiel ne feront pas l’objet d’une génération automatique.