Scaffolding (Reverse Engineering)

Il reverse engineering è il processo di scaffolding delle classi dei tipi di entità e di una DbContext classe basata su uno schema di database. Può essere eseguito usando il comando Scaffold-DbContext degli strumenti della console di EF Core Package Manager Console (PMC) o il comando dotnet ef dbcontext scaffold degli strumenti dell'interfaccia della riga di comando di .NET.

Nota

Lo scaffolding dei tipi di DbContext entità e documentati qui è diverso dallo scaffolding dei controller in ASP.NET Core con Visual Studio, che non è documentato qui.

Suggerimento

Se si usa Visual Studio, provare l'estensione della community di EF Core Power Tools . Questi strumenti offrono uno strumento grafico basato sugli strumenti da riga di comando di EF Core e offre opzioni aggiuntive per il flusso di lavoro e la personalizzazione.

Prerequisiti

  • Prima di eseguire lo scaffolding, è necessario installare gli strumenti PMC, che funzionano solo in Visual Studio o negli strumenti dell'interfaccia della riga di comando di .NET, che in tutte le piattaforme supportate da .NET.
  • Installare il pacchetto NuGet per Microsoft.EntityFrameworkCore.Design nel progetto in cui si esegue lo scaffolding.
  • Installare il pacchetto NuGet per il provider di database destinato allo schema del database da cui eseguire lo scaffolding.

Argomenti necessari

Sia il PMC che i comandi dell'interfaccia della riga di comando .NET hanno due argomenti obbligatori: il stringa di connessione al database e il provider di database EF Core da usare.

Stringa di connessione

Il primo argomento del comando è una stringa di connessione al database. Gli strumenti useranno questa stringa di connessione per leggere lo schema del database.

La modalità di citazione ed escape del stringa di connessione dipende dalla shell usata per eseguire il comando. Per altre informazioni, vedere la documentazione della shell. Ad esempio, PowerShell richiede l'escape del carattere di $, ma non \.

Nell'esempio seguente vengono scaffolding i tipi di entità e un DbContext oggetto del Chinook database che si trova nell'istanza di SQL Server Local DB del computer, che usa il Microsoft.EntityFrameworkCore.SqlServer provider di database.

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

Segreti utente per stringa di connessione

Se è presente un'applicazione .NET che usa il modello di hosting e il sistema di configurazione, ad esempio un progetto ASP.NET Core, è possibile usare la sintassi Name=<connection-string> per leggere la stringa di connessione dalla configurazione.

Si consideri, ad esempio, un'applicazione ASP.NET Core con il file di configurazione seguente:

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

Questo stringa di connessione nel file di configurazione può essere usato per eseguire lo scaffolding da un database usando:

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

Tuttavia, l'archiviazione di stringa di connessione nei file di configurazione non è una buona idea, poiché è troppo facile esporle accidentalmente, ad esempio eseguendo il push nel controllo del codice sorgente. Al contrario, le stringa di connessione devono essere archiviate in modo sicuro, ad esempio l'uso di Azure Key Vault o, quando si lavora in locale, lo strumento Secret Manager, noto come "Segreti utente".

Ad esempio, per usare i segreti utente, rimuovere prima di tutto il stringa di connessione dal file di configurazione di ASP.NET Core. Inizializzare quindi i segreti utente eseguendo il comando seguente nella stessa directory del progetto ASP.NET Core:

dotnet user-secrets init

Questo comando configura lo spazio di archiviazione nel computer separato dal codice sorgente e aggiunge una chiave per questa risorsa di archiviazione al progetto.

Archiviare quindi il stringa di connessione nei segreti utente. Ad esempio:

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

Ora lo stesso comando usato in precedenza il stringa di connessione denominato dal file di configurazione userà invece il stringa di connessione archiviato in Segreti utente. Ad esempio:

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

stringhe di Connessione ion nel codice con scaffolding

Per impostazione predefinita, lo scaffolder includerà il stringa di connessione nel codice con scaffolding, ma con un avviso. Ad esempio:

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");

Questa operazione viene eseguita in modo che il codice generato non si arresti in modo anomalo quando viene usato per la prima volta, che sarebbe un'esperienza di apprendimento molto scarsa. Tuttavia, come indicato nell'avviso, stringa di connessione non dovrebbero esistere nel codice di produzione. Vedere Durata, configurazione e inizializzazione di DbContext per i vari modi in cui è possibile gestire i stringa di connessione.

Suggerimento

È possibile passare l'opzione -NoOnConfiguring (VISUAL Studio PMC) o --no-onconfiguring (interfaccia della riga di comando .NET) per eliminare la creazione del OnConfiguring metodo contenente il stringa di connessione.

Nome provider

Il secondo argomento è il nome del provider. Il nome del provider è in genere uguale al nome del pacchetto NuGet del provider. Ad esempio, per SQL Server o AZURE SQL, usare Microsoft.EntityFrameworkCore.SqlServer.

Opzioni della riga di comando

Il processo di scaffolding può essere controllato da varie opzioni della riga di comando.

Specifica di tabelle e viste

Per impostazione predefinita, tutte le tabelle e le viste nello schema del database vengono scaffolding in tipi di entità. È possibile limitare le tabelle e le viste di cui è stato eseguito lo scaffolding specificando schemi e tabelle.

L'argomento -Schemas (VISUAL Studio PMC) o --schema (interfaccia della riga di comando .NET) specifica gli schemi di tabelle e viste per i quali verranno generati i tipi di entità. Se questo argomento viene omesso, vengono inclusi tutti gli schemi. Se si usa questa opzione, tutte le tabelle e le viste negli schemi verranno incluse nel modello, anche se non sono incluse in modo esplicito usando -Tables o --table.

L'argomento -Tables (VISUAL Studio PMC) o --table (interfaccia della riga di comando .NET) ha specificato le tabelle e le visualizzazioni per cui verranno generati i tipi di entità. È possibile includere tabelle o viste in uno schema specifico usando il formato 'schema.table' o 'schema.view'. Se questa opzione viene omessa, vengono incluse tutte le tabelle e le viste. |

Ad esempio, per eseguire lo scaffolding solo delle Artists tabelle e Albums :

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

Per eseguire lo scaffolding di tutte le tabelle e viste dagli Customer schemi e Contractor :

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

Ad esempio, per eseguire lo scaffolding della Purchases tabella dallo Customer schema e le Accounts tabelle e Contracts dallo Contractor schema:

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

Mantenimento dei nomi di database

Per impostazione predefinita, i nomi di tabella e di colonna vengono risolti in modo che corrispondano meglio alle convenzioni di denominazione .NET per i tipi e le proprietà. -UseDatabaseNames Se si specifica (Visual Studio PMC) o --use-database-names (interfaccia della riga di comando .NET) questo comportamento verrà disabilitato mantenendo i nomi di database originali il più possibile. Gli identificatori .NET non validi verranno comunque corretti e i nomi sintetizzati come le proprietà di navigazione saranno comunque conformi alle convenzioni di denominazione .NET.

Si considerino ad esempio le tabelle seguenti:

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);

Per impostazione predefinita, i tipi di entità seguenti verranno scaffolding da queste tabelle:

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>();
}

Tuttavia, usando -UseDatabaseNames o --use-database-names si ottengono i tipi di entità seguenti:

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!;
}

Usare attributi di mapping (nota anche come annotazioni dei dati)

I tipi di entità vengono configurati usando l'API ModelBuilder in per OnModelCreating impostazione predefinita. Specificare -DataAnnotations (PMC) o --data-annotations (interfaccia della riga di comando di .NET Core) per usare invece gli attributi di mapping, quando possibile.

Ad esempio, l'uso dell'API Fluent eseguirà lo scaffolding di quanto segue:

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

L'uso delle annotazioni dei dati eseguirà invece lo scaffolding di quanto segue:

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

Suggerimento

Alcuni aspetti del modello non possono essere configurati usando attributi di mapping. Lo scaffolder userà comunque l'API di compilazione del modello per gestire questi casi.

Nome della classe DbContext

Il nome della classe scaffolding DbContext sarà il nome del database suffisso con Context per impostazione predefinita. Per specificare un nome diverso, usare -Context in PMC e --context nell'interfaccia della riga di comando di .NET Core.

Directory di destinazione e spazi dei nomi

Le classi di entità e una classe DbContext vengono sottoposte a scaffolding nella directory radice del progetto e usano lo spazio dei nomi predefinito del progetto.

È possibile specificare la directory in cui è possibile eseguire lo scaffolding delle classi usando --output-dir ed è possibile usare --context-dir per eseguire lo scaffolding della classe DbContext in una directory separata dalle classi del tipo di entità:

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

Per impostazione predefinita, lo spazio dei nomi sarà lo spazio dei nomi radice più i nomi di qualsiasi sottodirectory nella directory radice del progetto. Tuttavia, è possibile eseguire l'override dello spazio dei nomi per tutte le classi di output usando --namespace. È anche possibile eseguire l'override dello spazio dei nomi solo per la classe DbContext usando --context-namespace:

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

Codice con scaffolding

Il risultato dello scaffolding da un database esistente è:

  • File contenente una classe che eredita da DbContext
  • Un file per ogni tipo di entità

Suggerimento

A partire da EF7, è anche possibile usare modelli di testo T4 per personalizzare il codice generato. Per altri dettagli, vedere Modelli di reverse engineering personalizzati.

Tipi di riferimento Nullable per C#

Lo scaffolder può creare modelli e tipi di entità ef che usano tipi di riferimento nullable C# (NRT). L'utilizzo di NRT viene eseguito automaticamente quando il supporto NRT è abilitato nel progetto C# in cui viene eseguito lo scaffolding del codice.

Ad esempio, la tabella seguente Tags contiene entrambe le colonne stringa nullable non nullable:

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

Ciò comporta le proprietà stringa nullable e non nullable corrispondenti nella classe generata:

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; }
}

Analogamente, le tabelle seguenti Posts contengono una relazione obbligatoria con la Blogs tabella :

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]));

Ciò comporta lo scaffolding della relazione non nullable (obbligatoria) tra i blog:

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; }
}

E 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; }
}

Relazioni molti-a-molti

Il processo di scaffolding rileva tabelle di join semplici e genera automaticamente un mapping molti-a-molti per tali tabelle. Si considerino, ad esempio, le tabelle per Posts e Tagse una tabella PostTag join che le connettono:

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);

Quando si esegue lo scaffolding, si ottiene una classe per 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; }
}

E una classe per 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; }
}

Ma nessuna classe per la PostTag tabella. Viene invece eseguito lo scaffolding della configurazione per una relazione molti-a-molti:

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");
            });

Altri linguaggi di programmazione

Pacchetti EF Core pubblicati dal codice C# di scaffolding Microsoft. Tuttavia, il sistema di scaffolding sottostante supporta un modello di plug-in per lo scaffolding in altri linguaggi. Questo modello di plug-in viene usato da vari progetti eseguiti dalla community, ad esempio:

Personalizzazione del codice

A partire da EF7, uno dei modi migliori per personalizzare il codice generato consiste nel personalizzare i modelli T4 usati per generarlo.

Il codice può anche essere modificato dopo la generazione, ma il modo migliore per eseguire questa operazione dipende dal fatto che si intenda eseguire nuovamente il processo di scaffolding quando il modello di database cambia.

Eseguire lo scaffolding una sola volta

Con questo approccio, il codice con scaffolding fornisce un punto di partenza per il mapping basato su codice in futuro. Tutte le modifiche apportate al codice generato possono essere apportate come desiderate: diventa codice normale esattamente come qualsiasi altro codice nel progetto.

È possibile mantenere sincronizzato il database e il modello di Entity Framework in uno dei due modi seguenti:

  • Passare all'uso delle migrazioni del database EF Core e usare i tipi di entità e la configurazione del modello EF come origine della verità, usando le migrazioni per guidare lo schema.
  • Aggiornare manualmente i tipi di entità e la configurazione di Entity Framework quando il database cambia. Ad esempio, se una nuova colonna viene aggiunta a una tabella, aggiungere una proprietà per la colonna al tipo di entità mappata e aggiungere qualsiasi configurazione necessaria usando attributi di mapping e/o codice in OnModelCreating. Si tratta di un processo relativamente semplice, con l'unica vera sfida che consiste nell'assicurarsi che le modifiche al database vengano registrate o rilevate in qualche modo in modo che gli sviluppatori responsabili del codice possano reagire.

Scaffolding ripetuto

Un approccio alternativo allo scaffolding una volta consiste nel ripetere lo scaffolding ogni volta che il database cambia. Verrà sovrascritto qualsiasi codice precedentemente sottoposto a scaffolding, vale a dire eventuali modifiche apportate ai tipi di entità o alla configurazione di Entity Framework nel codice andranno perse.

[SUGGERIMENTO] Per impostazione predefinita, i comandi di Entity Framework non sovrascriveranno alcun codice esistente per proteggersi da perdite accidentali di codice. L'argomento -Force (VISUAL Studio PMC) o --force (interfaccia della riga di comando .NET) può essere usato per forzare la sovrascrittura dei file esistenti.

Poiché il codice con scaffolding verrà sovrascritto, è consigliabile non modificarlo direttamente, ma basarsi invece su classi e metodi parziali e sui meccanismi in EF Core che consentono l'override della configurazione. In particolare:

  • Sia la DbContext classe che le classi di entità vengono generate come parziali. Ciò consente di introdurre membri e codice aggiuntivi in un file separato che non verrà sottoposto a override durante l'esecuzione dello scaffolding.
  • La DbContext classe contiene un metodo parziale denominato OnModelCreatingPartial. È possibile aggiungere un'implementazione di questo metodo alla classe parziale per .DbContext Verrà quindi chiamato dopo OnModelCreating la chiamata.
  • La configurazione del modello eseguita usando le API esegue l'override ModelBuilder di qualsiasi configurazione eseguita da convenzioni o attributi di mapping, nonché la configurazione precedente eseguita nel generatore di modelli. Ciò significa che il codice in OnModelCreatingPartial può essere usato per eseguire l'override della configurazione generata dal processo di scaffolding, senza la necessità di rimuovere tale configurazione.

Infine, tenere presente che a partire da EF7, i modelli T4 usati per generare codice possono essere personalizzati. Si tratta spesso di un approccio più efficace rispetto allo scaffolding con le impostazioni predefinite e quindi alla modifica con classi e/o metodi parziali.

Funzionamento

Per iniziare, il reverse engineering legge lo schema del database. Legge informazioni su tabelle, colonne, vincoli e indici.

Usa quindi le informazioni sullo schema per creare un modello di EF Core. Le tabelle vengono usate per creare tipi di entità, le colonne vengono usate per creare proprietà e le chiavi esterne vengono usate per creare relazioni.

Il modello viene infine usato per generare codice. Viene eseguito lo scaffolding di classi di tipi di entità corrispondenti, API Fluent e annotazioni dei dati per ricreare lo stesso modello dall'app.

Limiti

  • Non tutti gli elementi relativi a un modello possono essere rappresentati usando uno schema di database. Ad esempio, le informazioni su gerarchie di ereditarietà, tipi di proprietàe suddivisione delle tabelle non sono presenti nello schema del database. Per questo motivo, questi costrutti non verranno mai scaffolding.
  • È inoltre possibile che alcuni tipi di colonna non siano supportati dal provider EF Core. Queste colonne non verranno incluse nel modello.
  • È possibile definire i token di concorrenza in un modello di EF Core per impedire a due utenti di aggiornare contemporaneamente la stessa entità. Alcuni database hanno un tipo speciale per rappresentare questo tipo di colonna ,ad esempio rowversion in SQL Server, nel qual caso è possibile decompilare queste informazioni; Tuttavia, gli altri token di concorrenza non verranno scaffolding.