Plantillas personalizadas de ingeniería inversa

Nota:

Esta característica se agregó en EF Core 7.

Aunque en la utilización de técnicas de ingeniería inversa , Entity Framework Core se esfuerza por aplicar scaffolding a un buen código de uso general que se puede usar en una variedad de tipos de aplicaciones y usa convenciones de codificación comunes para una apariencia coherente y familiar. A veces, sin embargo, es deseable un código más especializado y estilos de codificación alternativos. En este artículo se muestra cómo personalizar el código con scaffolding mediante plantillas de texto T4.

Requisitos previos

En este artículo se da por supuesto que está familiarizado con la utilización de técnicas de ingeniería inversa en EF Core. Si no es así, revise ese artículo antes de continuar.

Adición de las plantillas predeterminadas

El primer paso para personalizar el código con scaffolding es agregar las plantillas predeterminadas al proyecto. Las plantillas predeterminadas se realiza internamente en el EF Core cuando se utiliza la técnica de ingeniería inversa. Proporcionan un punto inicial para empezar a personalizar el código con scaffolding.

Comience instalando el paquete de plantilla de EF Core para dotnet new:

dotnet new install Microsoft.EntityFrameworkCore.Templates

Ahora puede agregar las plantillas predeterminadas al proyecto. Para ello, ejecute el siguiente comando desde el directorio del proyecto.

dotnet new ef-templates

Este comando agrega los siguientes archivos al proyecto.

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

La plantilla DbContext.t4 se usa para aplicar scaffolding a una clase DbContext para la base de datos y la plantilla EntityType.t4 se usa para aplicar scaffolding a las clases de tipo de entidad para cada tabla y vista de la base de datos.

Sugerencia

La extensión .t4 se usa (en lugar de .tt) para evitar que Visual Studio transforme las plantillas. En su lugar, las plantillas serán transformadas por EF Core.

Introducción a T4

Vamos abrir la plantilla DbContext.t4 e inspeccionar su contenido. Este archivo es una plantilla de texto T4. T4 es un lenguaje para generar texto mediante .NET. El código siguiente solo tiene fines ilustrativos; no representa el contenido completo del archivo.

Importante

Las plantillas de texto T4, especialmente las que generan código, pueden ser difíciles de leer sin resaltado de sintaxis. Si es necesario, busque una extensión en el editor de código que habilite el resaltado de sintaxis 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 #>;

Las primeras líneas que comienzan con <#@ se denominan directivas. Afectan a cómo se transforma la plantilla. En la tabla siguiente se describe brevemente cada tipo de directiva usada.

Directiva Descripción
template Especifica hostSpecific="true" que permite usar la Host propiedad dentro de la plantilla para acceder a los servicios de EF Core.
assembly Agrega referencias de ensamblado necesarias para compilar la plantilla.
parameter Declara los parámetros que serán pasados por EF Core al transformar la plantilla.
import Al igual que en C#, el uso de directivas hace que los espacios de nombres entren en el ámbito del código de plantilla.

Después de las directivas, la siguiente sección es llamada bloque de DbContext.t4 control. Un bloque de control estándar comienza con <# y termina con #>. El código que contiene se ejecutará al transformar la plantilla. Para obtener una lista de propiedades y métodos disponibles dentro de los bloques de control, consulte la claseTextTransformation.

Todo lo que esté fuera de un bloque de control se copiará directamente en la salida de la plantilla.

Un bloque de control de expresión comienza con<#=. El código que contiene se evaluará y el resultado se agregará a la salida de la plantilla. Estos son similares a los argumentos de cadena interpolados de C#.

Para obtener una explicación más detallada y completa de la sintaxis T4, consulte Escribir una plantilla de texto T4.

Personalización de los tipos de entidad

Ahora vamos a ver cómo se personaliza una plantilla. Por predeterminado, EF Core genera el código siguiente para las propiedades de navegación de colección.

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

El uso List<T> es un buen valor predeterminado para la mayoría de las aplicaciones. Sin embargo, si usa un marco basado en XAML como WPF, WinUI o .NET MAUI, a menudo quiere usar ObservableCollection<T> en su lugar para habilitar el enlace de datos.

Abrir la plantillaEntityType.t4 y busque dónde genera List<T>. Tiene este aspecto:

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

Reemplace List por ObservableCollection.

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

También es necesario agregar una using directiva al código con scaffolding. Los usos se especifican en una lista cerca de la parte superior de la plantilla. Agregue System.Collections.ObjectModel a la lista.

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

Pruebe los cambios mediante los comandos utilizados por las técnicas de ingeniería inversa. Los comandos usan automáticamente las plantillas dentro del proyecto.

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

Si ha ejecutado el comando anteriormente, agregue la opción--force para sobrescribir los archivos existentes.

Si lo hizo correctamente, las propiedades de navegación de la colección ahora deben usar ObservableCollection<T>.

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

Actualizando las plantillas

Al agregar las plantillas predeterminadas al proyecto, crea una copia de ellas en función de esa versión de EF Core. A medida que los errores son corregidos y las características se agregan en versiones posteriores de EF Core, es posible que las plantillas no estén actualizadas. Debe revisar los cambios realizados en las plantillas de EF Core y combinarlos con las plantillas personalizadas.

Una manera de revisar los cambios realizados en las plantillas de EF Core es usar Git para compararlos entre versiones. El siguiente comando clonará el repositorio EF Core y generará una diferencia de estos archivos entre las versiones 7.0.0 y 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

Otra manera de revisar los cambios es descargar las dos versiones de Microsoft.EntityFrameworkCore.Templates de NuGet, extraiga su contenido (puede cambiar las extensiones de archivo a .zip), y compare esos archivos.

Antes de agregar las plantillas predeterminadas a un nuevo proyecto, recuerde actualizar al paquete de plantilla de EF Core más reciente.

dotnet new update

Uso avanzado

Omitir el modelo de entrada

Los parámetros Model y EntityType representan una forma posible de asignación a la base de datos. Puede elegir ignorar o cambiar partes del modelo. Por ejemplo, es posible que los nombres de navegación que proporcionemos no sean ideales y puede reemplazarlos por los suyos propios al aplicar scaffolding al código. Otros elementos como los nombres de restricción y los filtros de índice solo se usan en Migraciones y se pueden omitir de forma segura desde el modelo si no pretende usar Migraciones con el código scaffolded. Del mismo modo, es posible que quiera omitir secuencias o restricciones predeterminadas si la aplicación no las usa.

Al realizar cambios avanzados como este, asegúrese de que el modelo resultante siga siendo compatible con la base de datos. Revisar el CÓDIGO SQL generado por dbContext.Database.GenerateCreateScript() es una buena manera de validarlo.

Clases de configuración de entidades

Para los modelos grandes, el método OnModelCreating de la clase DbContext puede ser inadministrablemente grande. Una manera de abordar esto es usar IEntityTypeConfiguration<T> clases. Consulte Creación y configuración de un modelo para obtener más información acerca de estas clases.

Para aplicar scaffolding a estas clases, puede usar una tercera plantilla denominada EntityTypeConfiguration.t4. Al igual que la plantilla EntityType.t4, se usa para cada tipo de entidad del modelo y usa el parámetro de la plantillaEntityType.

Aplicar scaffolding a otros tipos de archivos

El propósito principal para utilizar técnicas de ingeniería inversa en EF Core es aplicar scaffolding a los tipos de entidad y DbContext. Sin embargo, no hay nada en las herramientas que requieren que realmente se aplique scaffolding al código. Por ejemplo, podría aplicar scaffolding a un diagrama de relación de entidad mediante 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 #>
<#
        }
    }
#>
```