Engenharia reversa (scaffolding)
A engenharia reversa é o processo de classes de tipo de entidade de scaffolding e uma classe DbContext com base em um esquema de banco de dados. Ele pode ser executado usando o comando Scaffold-DbContext
das ferramentas PMC (Console do Gerenciador de Pacotes) do EF Core ou o comando dotnet ef dbcontext scaffold
das ferramentas da CLI (Interface de Linha de Comando) do .NET.
Observação
O scaffolding de um DbContext
e tipos de entidade documentados aqui é diferente do scaffolding de controladores no ASP.NET Core usando o Visual Studio, que não está documentado aqui.
Dica
Se você usar o Visual Studio, experimente a extensão da comunidade do EF Core Power Tools. Essas ferramentas fornecem uma ferramenta gráfica que se baseia nas ferramentas de linha de comando do EF Core e oferece opções adicionais de fluxo de trabalho e personalização.
Pré-requisitos
- Antes do scaffolding, você precisará instalar as ferramentas do PMC, que funcionam somente no Visual Studio, ou as ferramentas da CLI do .NET, que têm suporte em todas as plataformas compatíveis com o .NET.
- Instale o pacote NuGet para
Microsoft.EntityFrameworkCore.Design
o qual você está scaffolding no projeto. - Instale o pacote NuGet para o provedor de banco de dados que tem como destino o esquema de banco de dados do qual você deseja fazer o scaffolding.
Argumentos necessários
O PMC e os comandos da CLI do .NET têm dois argumentos necessários: a cadeia de conexão com o banco de dados e o provedor de banco de dados EF Core a ser usado.
Cadeia de conexão
Aviso
Este artigo usa um banco de dados local que não exige que o usuário seja autenticado. Os aplicativos de produção devem usar o fluxo de autenticação mais seguro disponível. Para obter mais informações sobre autenticação para aplicativos de teste e produção implantados, consulte Fluxos de autenticação seguros.
O primeiro argumento para o comando é uma cadeia de conexão com o banco de dados. As ferramentas usam essa cadeia de conexão para ler o esquema de banco de dados.
A forma como a cadeia de conexão é citada e escapada depende do shell usado para executar o comando. Consulte a documentação do shell. Por exemplo, o PowerShell requer escape $
, mas não \
.
O exemplo faz scaffolding de tipos de entidade e um DbContext
do banco de dados Chinook
localizado na instância SQL Server LocalDB da máquina, fazendo uso do provedor de banco de dados Microsoft.EntityFrameworkCore.SqlServer
.
dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer
Cadeias de conexão no código em que foi feito o scaffolding
Por padrão, quem faz o scaffolding incluirá a cadeia de conexão no código onde foi feito o scaffolding, mas com um aviso. Por exemplo:
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");
Isso é feito para que o código gerado não falhe quando usado pela primeira vez, o que seria uma experiência de aprendizado muito ruim. No entanto, como diz o aviso, as cadeias de conexão não devem existir no código de produção. Consulte Tempo de Vida, Configuração e Inicialização do DbContext Lifetime para obter as várias maneiras de gerenciar cadeias de conexão.
Dica
A opção -NoOnConfiguring
(PMC do Visual Studio PMC) ou --no-onconfiguring
(CLI do .NET) pode ser passada para suprimir a criação do método OnConfiguring
que contém a cadeia de conexão.
Nome do provedor
O segundo argumento é o nome do provedor. O nome do provedor normalmente é o mesmo que o nome do pacote NuGet do provedor. Por exemplo, para SQL Server ou SQL do Azure, use Microsoft.EntityFrameworkCore.SqlServer
.
Opções de linha de comando
O processo de scaffolding pode ser controlado por várias opções de linha de comando.
Como especificar tabelas e exibições
Por padrão, todas as tabelas e modos de exibição no esquema de banco de dados são estruturadas em tipos de entidade. Você pode limitar quais tabelas e modos de exibição passam por scaffolding especificando esquemas e tabelas.
O argumento -Schemas
(PMC do Visual Studio) ou --schema
(CLI do .NET) especifica os esquemas de tabelas e exibições para os quais os tipos de entidade serão gerados. Se esse argumento for omitido, todos os esquemas serão incluídos. Se essa opção for usada, todas as tabelas e exibições nos esquemas serão incluídas no modelo, mesmo que não sejam explicitamente incluídas usando -Tables
ou --table
.
O argumento -Tables
(PMC do Visual Studio) ou --table
(CLI do .NET) especificou as tabelas e exibições para as quais os tipos de entidade serão gerados. Tabelas ou exibições em um esquema específico podem ser incluídas usando o formato "schema.table" ou "schema.view". Se essa opção for omitida, todas as tabelas e modos d exibição serão incluídas. |
Por exemplo, para estruturar apenas as tabelas Artists
e Albums
:
dotnet ef dbcontext scaffold ... --table Artist --table Album
Para estruturar todas as tabelas e modos de exibição dos esquemas Customer
e Contractor
:
dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor
Por exemplo, para estruturar a tabela Purchases
do esquema Customer
e as tabelas Accounts
e Contracts
do esquema Contractor
:
dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts
Como preservar nomes de banco de dados
Os nomes de tabela e coluna são corrigidos para corresponder melhor às convenções de nomenclatura do .NET para tipos e propriedades por padrão. Especificar -UseDatabaseNames
(PMC do Visual Studio) ou --use-database-names
(CLI do .NET) desabilitará esse comportamento preservando os nomes de banco de dados originais o máximo possível. Os identificadores .NET inválidos ainda serão corrigidos e nomes sintetizados, como propriedades de navegação, ainda estarão em conformidade com as convenções de nomenclatura do .NET.
Por exemplo, considere a seguintes tabelas:
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);
Por padrão, os seguintes tipos de entidade serão scaffolded dessas tabelas:
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>();
}
No entanto, usar -UseDatabaseNames
ou --use-database-names
resulta nos seguintes tipos de entidade:
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!;
}
Usar atributos de mapeamento (também conhecidos como Anotações de Dados)
Os tipos de entidade são configurados usando a API ModelBuilder
em OnModelCreating
por padrão. Especifique -DataAnnotations
(PMC) ou --data-annotations
(CLI do .NET Core) para usar atributos de mapeamento quando possível.
Por exemplo, o uso da API Fluente fará o scaffold de:
entity.Property(e => e.Title)
.IsRequired()
.HasMaxLength(160);
Usar Anotações de Dados fará o scaffold de:
[Required]
[StringLength(160)]
public string Title { get; set; }
Dica
Alguns aspectos do modelo não podem ser configurados usando atributos de mapeamento. O scaffolder ainda usará a API de criação de modelos para lidar com esses casos.
Nome DbContext
O nome da classe DbContext
com scaffold será o nome do banco de dados sufixado com Contextco por padrão. Para especificar outro, use -Context
no PMC e --context
na CLI do .NET Core.
Diretórios e namespaces de destino
As classes de entidade e uma classe DbContext são agrupadas no diretório raiz do projeto e usam o namespace padrão do projeto.
Você pode especificar o diretório em que as classes são scaffolded usando --output-dir
, e --context-dir
pode ser usado para executar scaffold da classe DbContext em um diretório separado das classes de tipo de entidade:
dotnet ef dbcontext scaffold ... --context-dir Data --output-dir Models
Por padrão, o namespace será o namespace raiz mais os nomes de quaisquer subdiretórios no diretório raiz do projeto. No entanto, você pode substituir o namespace para todas as classes de saída usando --namespace
. Você também pode substituir o namespace apenas para a classe DbContext usando --context-namespace
:
dotnet ef dbcontext scaffold ... --namespace Your.Namespace --context-namespace Your.DbContext.Namespace
O código scaffolded
O resultado do scaffolding de um banco de dados existente é:
- Um arquivo que contém uma classe que herda de
DbContext
- Um arquivo para cada tipo de entidade
Dica
A partir do EF7, você também pode usar modelos de texto T4 para personalizar o código gerado. Confira Modelos de engenharia reversa personalizados para obter mais detalhes.
Tipos de referências anuláveis em C#
O scaffolder pode criar tipos de entidade e modelo EF que usam Tipos de referência anuláveis (NRTs) em C#. O uso de NRT passa por scaffolding automaticamente quando o suporte do NRT é habilitado no projeto C# no qual o código está sofrendo scaffolding..
Por exemplo, a seguinte tabela Tags
contém colunas de cadeia de caracteres anuláveis e não anuláveis:
CREATE TABLE [Tags] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
[Description] nvarchar(max) NULL,
CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));
O resultado disso são propriedades de cadeia de caracteres anuláveis e não anuláveis correspondentes na classe gerada:
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; }
}
Da mesma forma, as seguintes tabelas Posts
contêm uma relação obrigatória com a tabela 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]));
O resultado disso é o scaffolding da relação não anulável (obrigatória) entre os 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; }
}
E posta:
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; }
}
Relacionamento muitos para muitos
O processo de scaffolding detecta tabelas de junção simples e gera automaticamente um mapeamento muitos para muitos para elas. Por exemplo, considere tabelas para Posts
e Tags
, e uma tabela de junção PostTag
as conectando:
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 o scaffolding é feito, isso resulta em uma classe para 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 uma classe para 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; }
}
Mas nenhuma classe para a tabela PostTag
. Em vez disso, é feito scaffolding na configuração para uma relação muitos para muitos:
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");
});
Outras linguagens de programação
Os pacotes do EF Core publicados pelo código C# do scaffold da Microsoft. No entanto, o sistema de scaffolding subjacente é compatível com um modelo de plug-in para scaffolding para outras linguagens. Esse modelo de plug-in é usado por vários projetos de execução da comunidade, por exemplo:
- O EntityFrameworkCore.VisualBasic fornece suporte para o Visual Basic
- O EFCore.FSharp fornece suporte para F#
Como personalizar o código
A partir do EF7, uma das melhores maneiras de personalizar o código gerado é personalizando os modelos T4 usados para gerá-lo.
O código também pode ser alterado depois de gerado, mas a melhor maneira de fazer isso depende se você pretende executar novamente o processo de scaffolding quando o modelo de banco de dados é alterado.
Fazer scaffolding apenas uma vez
Com essa abordagem, o código que sofreu engenharia reversa fornece um ponto de partida para o mapeamento baseado em código daqui para frente. Todas as alterações no código gerado podem ser feitas conforme desejado. Ela se torna um código normal, assim como qualquer outro código em seu projeto.
Manter o banco de dados e o modelo EF em sincronia pode ser feito de duas maneiras:
- Alterne para usar migrações de banco de dados do EF Core e use os tipos de entidade e a configuração do modelo EF como a fonte da verdade, usando migrações para conduzir o esquema.
- Atualize manualmente os tipos de entidade e a configuração do EF quando o banco de dados for alterado. Por exemplo, se uma nova coluna for adicionada a uma tabela, adicione uma propriedade para a coluna ao tipo de entidade mapeada e adicione as configurações necessárias usando atributos de mapeamento e/ou código em
OnModelCreating
. Isso é relativamente fácil, e o único desafio real é garantir que as alterações no banco de dados sejam registradas ou detectadas de alguma forma para que os desenvolvedores responsáveis pelo código possam reagir.
Engenharia reversa repetida
Uma abordagem alternativa para fazer engenharia reversa uma vez é fazê-la novamente sempre que o banco de dados for alterado. Isso substituirá qualquer código que passou por engenharia reversa anteriormente, o que significa que quaisquer alterações feitas em tipos de entidade ou configuração de EF nesse código serão perdidas.
[DICA] Por padrão, os comandos EF não substituirão nenhum código existente para proteger contra perda acidental de código. O argumento
-Force
(PMC do Visual Studio) ou--force
(CLI do .NET) pode ser usado para forçar a substituição de arquivos existentes.
Como o código de engenharia reversa será substituído, é melhor não modificá-lo diretamente, mas sim contar com classes e métodos parciais e com os mecanismos no EF Core que permitem que a configuração seja substituída. Especificamente:
- As classes
DbContext
e as classes de entidade são geradas como parciais. Isso permite a introdução de membros adicionais e código em um arquivo separado que não será substituído quando a engenharia reversa for executada. - A classe
DbContext
contém um método parcial chamadoOnModelCreatingPartial
. Uma implementação desse método pode ser adicionada à classe parcial para oDbContext
. Em seguida, ele será chamado depois queOnModelCreating
for chamado. - A configuração de modelo feita usando as APIs do
ModelBuilder
substitui qualquer configuração feita por convenções ou atributos de mapeamento, bem como a configuração anterior feita no construtor de modelos. Isso significa que o códigoOnModelCreatingPartial
pode ser usado para substituir a configuração gerada pelo processo de engenharia reversa, sem a necessidade de remover essa configuração.
Por fim, lembre-se de que, a partir do EF7, os modelos T4 usados para gerar código podem ser personalizados. Geralmente, essa é uma abordagem mais eficaz do que a engenharia reversa com os padrões e, em seguida, a modificação com classes parciais e/ou métodos.
Como ele funciona
A engenharia reversa começa lendo o esquema de banco de dados. Ela lê informações sobre tabelas, colunas, restrições e índices.
Em seguida, usa as informações de esquema para criar um modelo EF Core. As tabelas são usadas para criar tipos de entidade, colunas são usadas para criar propriedades e chaves estrangeiras são usadas para criar relações.
Por fim, o modelo é usado para gerar código. As classes de tipo de entidade correspondentes, a API fluente e as anotações de dados são scaffolded para recriar o mesmo modelo de seu aplicativo.
Limitações
- Nem tudo sobre um modelo pode ser representado usando um esquema de banco de dados. Por exemplo, informações sobre hierarquias de herança, tipos de propriedade e divisão de tabelas não estão presentes no esquema de banco de dados. Por isso, essas construções nunca passarão por engenharia reversa.
- Além disso, alguns tipos de coluna podem não ter suporte do provedor EF Core. Essas colunas não serão incluídas no modelo.
- Você pode definir tokens de simultaneidade em um modelo EF Core para impedir que dois usuários atualizem a mesma entidade ao mesmo tempo. Alguns bancos de dados têm um tipo especial para representar esse tipo de coluna (por exemplo, rowversion no SQL Server). Nesse caso, podemos fazer engenharia reversa dessas informações. No entanto, outros tokens de simultaneidade não serão passarão por engenharia reversa.