Scaffolding (utilización de técnicas de ingeniería inversa)

Las técnicas de ingeniería inversa constituyen el proceso de scaffolding de clases de tipo de entidad y una clase DbContext basada en un esquema de base de datos. Se puede realizar mediante el comando Scaffold-DbContext de las herramientas de consola del administrador de paquetes (PMC) de EF Core o el comando dotnet ef dbcontext scaffold de las herramientas de la interfaz de línea de comandos (CLI) de .NET.

Nota:

El scaffolding de un DbContext y los tipos de entidad que se documentan aquí es distinto del scaffolding de controladores en ASP.NET Core mediante Visual Studio, que no se documenta aquí.

Sugerencia

Si usa Visual Studio, pruebe la extensión de la comunidad de Power Tools de EF Core. Estas herramientas proporcionan una herramienta gráfica que se basa en las herramientas de línea de comandos de EF Core y ofrece opciones adicionales de flujo de trabajo y personalización.

Requisitos previos

  • Antes de aplicar scaffolding, deberá instalar las herramientas de PMC, que solo funcionan en Visual Studio, o las herramientas de la CLI de .NET, que se admiten en todas las plataformas compatibles con .NET.
  • Instale el paquete NuGet para Microsoft.EntityFrameworkCore.Design en el proyecto al que va a aplicar scaffolding.
  • Instale el paquete NuGet para el proveedor de bases de datos que tiene como destino el esquema de base de datos desde el que quiere aplicar scaffolding.

Argumentos necesarios

Tanto PMC como los comandos de la CLI de .NET tienen dos argumentos necesarios: la cadena de conexión a la base de datos y el proveedor de bases de datos de EF Core que se va a usar.

Cadena de conexión

El primer argumento para el comando es una cadena de conexión a la base de datos. Las herramientas usarán esta cadena de conexión para leer el esquema de base de datos.

La forma en que se aplican comillas y escape a la cadena de conexión depende del shell que use para ejecutar el comando; consulte la documentación del shell para obtener más información. Por ejemplo, PowerShell requiere que se escape el carácter $, pero no \.

En el ejemplo siguiente se aplica scaffolding a los tipos de entidad y a DbContext desde la base de datos Chinook ubicada en la instancia LocalDB de SQL Server del equipo, utilizando el proveedor de base de datos Microsoft.EntityFrameworkCore.SqlServer.

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

Secretos de usuario para cadenas de conexión

Si tiene una aplicación .NET que usa el modelo de hospedaje y el sistema de configuración, como un proyecto de ASP.NET Core, puede usar la sintaxis Name=<connection-string> para leer la cadena de conexión de la configuración.

Por ejemplo, piense en una aplicación ASP.NET Core con el siguiente archivo de configuración:

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

Esta cadena de conexión del archivo de configuración se puede usar para aplicar scaffolding desde una base de datos mediante:

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

Sin embargo, almacenar cadenas de conexión en archivos de configuración no es una buena idea, ya que es demasiado fácil exponerlas accidentalmente, por ejemplo, insertando en el control de código fuente. Alternativamente, las cadenas de conexión deben almacenarse de forma segura, como usar Azure Key Vault o, al trabajar localmente, la herramienta Secret Manager, también conocida como "Secretos de usuario".

Por ejemplo, para usar Secretos de usuario, quite primero la cadena de conexión del archivo de configuración de ASP.NET Core. A continuación, para inicializar Secretos de usuario, ejecute el siguiente comando en el mismo directorio que el proyecto de ASP.NET Core:

dotnet user-secrets init

Este comando configura el almacenamiento en el equipo independiente del código fuente y agrega una clave para este almacenamiento al proyecto.

A continuación, almacene la cadena de conexión en Secretos de usuario. Por ejemplo:

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

Ahora, el mismo comando que usó la cadena de conexión con nombre del archivo de configuración usará la cadena de conexión almacenada en Secretos de usuario. Por ejemplo:

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

Cadenas de conexión en el código con scaffolding

De forma predeterminada, el autor del scaffolding incluirá la cadena de conexión en el código con scaffolding, pero con una advertencia. Por ejemplo:

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

Esto se hace para que el código generado no se bloquee cuando se usa por primera vez, lo que sería una experiencia de aprendizaje muy deficiente. Sin embargo, como indica la advertencia, las cadenas de conexión no deben existir en el código de producción. Consulte Duración, configuración e inicialización de DbContext para conocer las distintas formas en que se pueden administrar las cadenas de conexión.

Sugerencia

La opción -NoOnConfiguring (PMC de Visual Studio) o --no-onconfiguring (CLI de .NET) se puede pasar para suprimir la creación del método OnConfiguring que contiene la cadena de conexión.

Nombre del proveedor

El segundo argumento es el nombre del proveedor. El nombre del proveedor suele ser el mismo que el nombre del paquete NuGet del proveedor. Por ejemplo, para SQL Server o Azure SQL, use Microsoft.EntityFrameworkCore.SqlServer.

Opciones de línea de comandos

El proceso de scaffolding se puede controlar mediante varias opciones de línea de comandos.

Especificación de tablas y vistas

De forma predeterminada, se aplica scaffolding a todas las tablas y vistas del esquema de base de datos en tipos de entidad. Puede limitar a qué tablas y vistas se aplica scaffolding especificando esquemas y tablas.

El argumento -Schemas (PMC de Visual Studio) o --schema (CLI de .NET) especifica los esquemas de tablas y vistas para los que se generarán los tipos de entidad. Si se omite este argumento, se incluyen todos los esquemas. Si se usa esta opción, todas las tablas y vistas de los esquemas se incluirán en el modelo, aunque no se incluyan explícitamente mediante -Tables o --table.

El argumento -Tables (PMC de Visual Studio) o --table (CLI de .NET) especificó las tablas y vistas para las que se generarán los tipos de entidad. Las tablas o vistas de un esquema específico se pueden incluir mediante el formato "schema.table" o "schema.view". Si se omite esta opción, se incluyen todas las tablas y vistas. |

Por ejemplo, para aplicar scaffolding solo a las tablas Artists y Albums:

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

Para aplicar scaffolding a todas las tablas y vistas de los esquemas Customer y Contractor:

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

Por ejemplo, para aplicar scaffolding a la tabla Purchases del esquema Customer y a las tablas Accounts y Contracts del esquema Contractor:

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

Conservación de nombres de base de datos

Los nombres de tabla y columna se corrigen para que coincidan mejor con las convenciones de nomenclatura de .NET para los tipos y las propiedades de forma predeterminada. Al especificar -UseDatabaseNames (PMC de Visual Studio) o --use-database-names (CLI de.NET) se deshabilitará este comportamiento, conservando los nombres de base de datos originales tanto como sea posible. Los identificadores de .NET no válidos seguirán siendo fijos y los nombres sintetizados, como las propiedades de navegación, seguirán siendo conformes a las convenciones de nomenclatura de .NET.

Considere, por ejemplo, las siguientes tablas:

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

De forma predeterminada, se aplicará scaffolding a los siguientes tipos de entidad desde estas tablas:

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

Sin embargo, el uso de -UseDatabaseNames o --use-database-names da como resultado los siguientes tipos de entidad:

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

Uso de atributos de asignación (también conocidos como anotaciones de datos)

Los tipos de entidad se configuran mediante la API de ModelBuilder en OnModelCreating de manera predeterminada. Especifique -DataAnnotations (PMC) o --data-annotations (CLI de .NET Core) para usar atributos de asignación, alternativamente, siempre que sea posible.

Por ejemplo, el uso de la API fluida aplicará scaffolding a esto:

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

Mientras se usan anotaciones de datos, se aplicará scaffolding a esto:

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

Sugerencia

Algunos aspectos del modelo no se pueden configurar mediante atributos de asignación. El autor del scaffolding seguirá usando la API de creación de modelos para controlar estos casos.

Nombre de DbContext

El nombre de la clase DbContext con scaffolding será el nombre de la base de datos con el sufijo Context de forma predeterminada. Para especificar una diferente, use -Context en PMC y --context en la CLI de .NET Core.

Directorios y espacios de nombres de destino

A las clases de entidad y a una clase DbContext se las aplican scaffolding en el directorio raíz del proyecto y usan el espacio de nombres predeterminado del proyecto.

Puede especificar el directorio donde se aplica scaffolding a las clases mediante --output-dir y --context-dir se puede usar para aplicar scaffolding a la clase DbContext en un directorio independiente de las clases de tipo de entidad:

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

De forma predeterminada, el espacio de nombres será el espacio de nombres raíz más los nombres de cualquier subdirectorio en el directorio raíz del proyecto. Sin embargo, puede invalidar el espacio de nombres para todas las clases de salida mediante --namespace. También puede invalidar el espacio de nombres solo para la clase DbContext mediante --context-namespace:

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

El código con scaffolding

El resultado del scaffolding desde una base de datos existente es:

  • Un archivo que contiene una clase que hereda de DbContext
  • Un archivo para cada tipo de entidad

Sugerencia

A partir de EF7, también puede usar plantillas de texto T4 para personalizar el código generado. Consulte Plantillas de ingeniería inversa personalizadas para obtener más detalles.

Tipos de referencia que aceptan valores NULL de C#

El autor del scaffolding puede crear un modelo de EF y tipos de entidad que usan tipos de referencia que aceptan valores NULL (NRT) de C#. Se aplica scaffolding automáticamente al uso de NRT cuando se habilita la compatibilidad con NRT en el proyecto de C# en el que se aplica scaffolding al código.

Por ejemplo, la tabla Tags siguiente contiene tanto columnas de cadena que aceptan valores NULL como que no:

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

Esto da como resultado las correspondientes propiedades de cadena que aceptan y que no aceptan valores NULL en la clase generada:

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 forma similar, la tabla Posts siguiente contiene una relación necesaria con la tabla 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]));

Esto da como resultado el scaffolding de la relación que no acepta valores NULL (obligatorio) entre 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; }
}

Y publicaciones:

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

Relaciones de varios a varios

El proceso de scaffolding detecta tablas de combinación simples y genera automáticamente una asignación de varios a varios para ellas. Por ejemplo, considere las tablas para Posts y Tags, y una tabla de combinación PostTag que las conecta:

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

Cuando se aplica scaffolding, el resultado es una clase 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; }
}

Y una clase 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; }
}

Pero ninguna clase para la tabla PostTag. En su lugar, se aplica scaffolding a la configuración de una relación de varios a varios:

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

Otros lenguajes de programación

Los paquetes de EF Core publicados por el código de C# de scaffolding de Microsoft. Sin embargo, el sistema de scaffolding subyacente admite un modelo de complemento para aplicar scaffolding a otros lenguajes. Este modelo de complemento lo usan varios proyectos de ejecución por la comunidad, por ejemplo:

Personalizar el código

A partir de EF7, una de las mejores maneras de personalizar el código generado es personalizar las plantillas de T4 que se usan para generarlo.

El código también se puede cambiar después de generarlo, pero la mejor manera de hacerlo depende de si tiene previsto volver a ejecutar el proceso de scaffolding cuando cambia el modelo de base de datos.

Aplicar scaffolding una sola vez

Con este enfoque, el código con scaffolding proporciona un punto de partida para la asignación basada en código en el futuro. Los cambios realizados en el código generado se pueden realizar como quiera; se convierte en código normal como cualquier otro código del proyecto.

Mantener la base de datos y el modelo de EF sincronizados se puede realizar de una de estas dos maneras:

  • Cambie al uso de migraciones de bases de datos de EF Core y use los tipos de entidad y la configuración del modelo de EF como única fuente de información, mediante migraciones para controlar el esquema.
  • Actualice manualmente la configuración de EF y los tipos de entidad cuando cambie la base de datos. Por ejemplo, si se agrega una nueva columna a una tabla, agregue una propiedad para la columna al tipo de entidad asignada y agregue cualquier configuración necesaria mediante atributos de asignación y/o código en OnModelCreating. Esto es relativamente fácil, ya que el único desafío real es un proceso para asegurarse de que los cambios de la base de datos se registran o detectan de alguna manera para que los desarrolladores responsables del código puedan reaccionar.

Scaffolding repetido

Un enfoque alternativo a aplicar scaffolding una vez consiste en volver a aplicar scaffolding cada vez que cambia la base de datos. Esto sobrescribirá cualquier código con scaffolding anterior, lo que significa que se perderán los cambios realizados en los tipos de entidad o la configuración de EF en ese código.

[SUGERENCIA] De forma predeterminada, los comandos de EF no sobrescribirán ningún código existente para protegerse contra la pérdida accidental de código. El argumento -Force (PMC de Visual Studio) o --force (CLI de .NET) se puede usar para forzar la sobrescritura de los archivos existentes.

Dado que el código con scaffolding se sobrescribirá, es mejor no modificarlo directamente, sino basarse en clases y métodos parciales y los mecanismos de EF Core que permiten invalidar la configuración. Concretamente:

  • Tanto la clase DbContext como las clases de entidad se generan como parciales. Esto permite introducir miembros y código adicionales en un archivo independiente que no se invalidará cuando se ejecute scaffolding.
  • La clase DbContext contiene un método parcial denominado OnModelCreatingPartial. Se puede agregar una implementación de este método a la clase parcial para DbContext. Posteriormente, se llamará después de llamar a OnModelCreating.
  • La configuración del modelo realizada mediante las API de ModelBuilder invalida cualquier configuración realizada por convenciones o atributos de asignación, así como la configuración anterior realizada en el generador de modelos. Esto significa que el código de OnModelCreatingPartial se puede usar para invalidar la configuración generada por el proceso de scaffolding, sin necesidad de quitar esa configuración.

Por último, recuerde que a partir de EF7, se pueden personalizar las plantillas de T4 que se usan para generar código. A menudo, se trata de un enfoque más eficaz que aplica scaffolding con los valores predeterminados y, posteriormente, realizar la modificación con métodos y/o clases parciales.

Funcionamiento

La ingeniería inversa comienza leyendo el esquema de la base de datos. Lee información sobre tablas, columnas, restricciones e índices.

A continuación, usa la información de esquema para crear un modelo de EF Core. Las tablas se usan para crear tipos de entidad; las columnas se usan para crear propiedades; y las claves externas se usan para crear relaciones.

Por último, el modelo se usa para generar código. Se aplica scaffolding a las clases de tipo de entidad correspondientes, a la API fluida y a las anotaciones de datos para volver a crear el mismo modelo a partir de la aplicación.

Limitaciones

  • No todo sobre un modelo se puede representar mediante un esquema de base de datos. Por ejemplo, la información sobre las jerarquías de herencia, los tipos de propiedad y la división de tablas no están presentes en el esquema de la base de datos. Debido a esto, nunca se aplicará scaffolding a estas construcciones.
  • Además, es posible que el proveedor de EF Core no admita algunos tipos de columna. Estas columnas no se incluirán en el modelo.
  • Puede definir tokens de simultaneidad, en un modelo de EF Core para evitar que dos usuarios actualicen la misma entidad al mismo tiempo. Algunas bases de datos tienen un tipo especial para representar este tipo de columna (por ejemplo, rowversion en SQL Server) en cuyo caso, podemos aplicar ingeniería inversa a esta información; sin embargo, a otros tokens de simultaneidad no se les aplicará scaffolding.