Compartir a través de


Configuración masiva del modelo

Cuando es necesario configurar un aspecto de la misma manera en varios tipos de entidad, las siguientes técnicas permiten reducir la duplicación de código y consolidar la lógica.

Consulte el proyecto de ejemplo completo que contiene los fragmentos de código que se presentan a continuación.

Configuración masiva en OnModelCreating

Cada objeto de generador devuelto de ModelBuilder expone una propiedad Model o Metadata que proporciona un acceso de bajo nivel a los objetos que componen el modelo. En concreto, hay métodos que permiten iterar sobre objetos específicos del modelo y aplicarles una configuración común.

En el siguiente ejemplo, el modelo contiene un valor personalizado de tipo Currency.

public readonly struct Currency
{
    public Currency(decimal amount)
        => Amount = amount;

    public decimal Amount { get; }

    public override string ToString()
        => $"${Amount}";
}

Las propiedades de este tipo no se detectan de forma predeterminada, ya que el proveedor de EF actual no sabe cómo asignarlo a un tipo de base de datos. Este fragmento de código de OnModelCreating agrega todas las propiedades del tipo Currency y configura un convertidor de valores en un tipo admitido: decimal:

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    foreach (var propertyInfo in entityType.ClrType.GetProperties())
    {
        if (propertyInfo.PropertyType == typeof(Currency))
        {
            entityType.AddProperty(propertyInfo)
                .SetValueConverter(typeof(CurrencyConverter));
        }
    }
}
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}

Desventajas de la API de metadatos

  • A diferencia de Fluent API, todas las modificaciones del modelo deben realizarse explícitamente. Por ejemplo, si algunas de las Currency propiedades se configuraron como navegaciones mediante una convención, primero debe quitar la navegación que hace referencia a la propiedad CLR antes de agregar una propiedad de tipo de entidad para ella. #9117 mejorará esto.
  • Las convenciones se ejecutan después de cada cambio. Si quita una navegación detectada por una convención, la convención se ejecutará de nuevo y podría volver a agregarla. Para evitar que esto suceda, tendría que retrasar las convenciones hasta después de agregar la propiedad llamando a DelayConventions() y luego eliminar el objeto devuelto, o marcar la propiedad CLR como ignorada mediante AddIgnored.
  • Los tipos de entidad se pueden agregar después de que se produzca esta iteración y la configuración no se aplicará a ellos. Normalmente, esto se puede evitar colocando este código al final de OnModelCreating, pero si tiene dos conjuntos interdependientes de configuraciones, es posible que no haya un orden que les permita aplicarse de forma coherente.

Configuración previa a la convención

EF Core permite especificar la configuración de mapeo una vez para un tipo CLR determinado; luego esa configuración se aplica a todas las propiedades de ese tipo en el modelo a medida que se van descubriendo. Esto se denomina "configuración del modelo anterior a la convención", ya que configura aspectos del modelo antes de que se permitan ejecutar las convenciones de creación de modelos. Dicha configuración se aplica invalidando ConfigureConventions en el tipo derivado de DbContext.

En este ejemplo se muestra cómo configurar todas las propiedades de tipo Currency para tener un convertidor de valores:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<Currency>()
        .HaveConversion<CurrencyConverter>();
}

Y en este ejemplo se muestra cómo configurar algunas facetas en todas las propiedades de tipo string:

configurationBuilder
    .Properties<string>()
    .AreUnicode(false)
    .HaveMaxLength(1024);

Nota:

El tipo especificado en una llamada desde ConfigureConventions puede ser un tipo base, una interfaz o una definición de tipo genérico. Todas las configuraciones coincidentes se aplicarán en orden a partir de lo menos específico:

  1. Interfaz
  2. Tipo base
  3. Definición de tipo genérico
  4. Tipo de valor no anulable
  5. Tipo exacto

Importante

La configuración previa a la convención es equivalente a la configuración explícita que se aplica en cuanto se agrega un objeto coincidente al modelo. Invalidará todas las convenciones y las anotaciones de datos. Por ejemplo, con la configuración anterior, todas las propiedades de clave externa de cadena se crearán como no unicode con MaxLength de 1024, incluso cuando esto no coincida con la clave principal.

Omitir tipos

La configuración previa a la convención también permite omitir un tipo y evitar que se detecte mediante convenciones como un tipo de entidad o como una propiedad en un tipo de entidad:

configurationBuilder
    .IgnoreAny(typeof(IList<>));

Asignación de tipos predeterminada

Por lo general, EF puede traducir consultas con constantes de un tipo que no es compatible con el proveedor, siempre y cuando haya especificado un convertidor de valores para una propiedad de este tipo. Sin embargo, en las consultas que no implican ninguna propiedad de este tipo, no hay forma de que EF encuentre el convertidor de valores correcto. En este caso, es posible llamar DefaultTypeMapping a para agregar o invalidar una asignación de tipos de proveedor:

configurationBuilder
    .DefaultTypeMapping<Currency>()
    .HasConversion<CurrencyConverter>();

Limitaciones de la configuración previa a la convención

  • Muchos aspectos no se pueden configurar con este enfoque. #6787 expandirá esto a más tipos.
  • Actualmente, la configuración solo viene determinada por el tipo CLR. #20418 permitiría predicados personalizados.
  • Esta configuración se realiza antes de crear un modelo. Si surgen conflictos al aplicarlo, el seguimiento de la pila de excepciones no contendrá el ConfigureConventions método , por lo que podría ser más difícil encontrar la causa.

Convenciones

Las convenciones de compilación de modelos de EF Core son clases que contienen lógica que se desencadena en función de los cambios realizados en el modelo a medida que se compila. Esto mantiene el modelo up-to-date a medida que se realiza la configuración explícita, se aplican los atributos de asignación y se ejecutan otras convenciones. Para participar en esto, cada convención implementa una o varias interfaces que determinan cuándo se desencadenará el método correspondiente. Por ejemplo, una convención que implementa IEntityTypeAddedConvention se desencadenará cada vez que se agregue un nuevo tipo de entidad al modelo. Del mismo modo, una convención que implementa ambos IForeignKeyAddedConvention y IKeyAddedConvention se activará cada vez que se agregue una clave o una clave externa al modelo.

Las convenciones de creación de modelos son una forma eficaz de controlar la configuración del modelo, pero pueden ser complejas y difíciles de obtener correctamente. En muchos casos, la configuración del modelo anterior a la convención se puede usar en su lugar para especificar fácilmente la configuración común para las propiedades y los tipos.

Adición de una nueva convención

Ejemplo: Restricción de la longitud de las propiedades discriminatorias

La estrategia de asignación de herencia de tabla por jerarquía requiere una columna discriminadora para especificar qué tipo se representa en una fila dada. De forma predeterminada, EF usa una columna de cadena no limitada para el discriminador, lo que garantiza que funcionará para cualquier longitud de discriminador. Sin embargo, restringir la longitud máxima de las cadenas discriminatorias puede hacer que el almacenamiento y las consultas sean más eficaces. Vamos a crear una nueva convención que lo hará.

Las convenciones de compilación de modelos de EF Core se desencadenan en función de los cambios realizados en el modelo a medida que se compila. Esto mantiene el modelo up-to-date a medida que se realiza la configuración explícita, se aplican los atributos de asignación y se ejecutan otras convenciones. Para participar en esto, cada convención implementa una o varias interfaces que determinan cuándo se desencadenará la convención. Por ejemplo, una convención que implementa IEntityTypeAddedConvention se desencadenará cada vez que se agregue un nuevo tipo de entidad al modelo. Del mismo modo, una convención que implementa ambos IForeignKeyAddedConvention y IKeyAddedConvention se activará cada vez que se agregue una clave o una clave externa al modelo.

Saber qué interfaces implementar pueden ser complicadas, ya que la configuración realizada en el modelo en un punto puede cambiarse o quitarse en un momento posterior. Por ejemplo, una clave se puede crear por convención, pero posteriormente se reemplaza cuando se configura explícitamente una clave diferente.

Vamos a hacer esto un poco más concreto mediante un intento inicial de implementar la convención de longitud del discriminador.

public class DiscriminatorLengthConvention1 : IEntityTypeBaseTypeChangedConvention
{
    public void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        var discriminatorProperty = entityTypeBuilder.Metadata.FindDiscriminatorProperty();
        if (discriminatorProperty != null
            && discriminatorProperty.ClrType == typeof(string))
        {
            discriminatorProperty.Builder.HasMaxLength(24);
        }
    }
}

Esta convención implementa IEntityTypeBaseTypeChangedConvention, lo que significa que se desencadenará cada vez que se cambie la jerarquía de herencia asignada para un tipo de entidad. A continuación, la convención busca y configura la propiedad de discriminador de cadenas para la jerarquía.

A continuación, se usa esta convención llamando a Add en ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ =>  new DiscriminatorLengthConvention1());
}

Nota:

En lugar de agregar una instancia de la convención directamente, el Add método acepta un generador para crear instancias de la convención. Esto permite que la convención use dependencias del proveedor de servicios interno de EF Core. Dado que esta convención no tiene dependencias, el parámetro del proveedor de servicios se denomina _, lo que indica que nunca se usa.

Al compilar el modelo y examinar el Post tipo de entidad se muestra que esto ha funcionado: la propiedad discriminadora ahora está configurada para con una longitud máxima de 24:

 Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

¿Qué ocurre si ahora configuramos explícitamente una propiedad diferente de discriminador? Por ejemplo:

modelBuilder.Entity<Post>()
    .HasDiscriminator<string>("PostTypeDiscriminator")
    .HasValue<Post>("Post")
    .HasValue<FeaturedPost>("Featured");

Al examinar la vista de depuración del modelo, encontramos que la longitud del discriminador ya no está configurada.

 PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw

Esto se debe a que la propiedad de discriminador que configuramos en nuestra convención se eliminó más tarde cuando se agregó el discriminador personalizado. Podríamos intentar corregirlo implementando otra interfaz en nuestra convención para reaccionar a los cambios discriminatorios, pero averiguar qué interfaz implementar no es fácil.

Afortunadamente, hay un enfoque más sencillo. A menudo, no importa cómo luce el modelo mientras se está construyendo, siempre que el modelo final sea correcto. Además, la configuración que queremos aplicar a menudo no necesita desencadenar otras convenciones para reaccionar. Por lo tanto, nuestra convención puede implementar IModelFinalizingConvention. Las convenciones de finalización del modelo se ejecutan una vez completada la compilación del modelo y, por tanto, tienen acceso al estado casi final del modelo. Esto se opone a las convenciones interactivas que reaccionan a cada cambio de modelo y se asegura de que el modelo está up-to-date en cualquier punto de la ejecución del OnModelCreating método. Una convención de finalización de modelos normalmente recorrerá todo el modelo configurando sus elementos a medida que avanza. Por lo tanto, en este caso, encontraremos todos los discriminadores en el modelo y los configuraremos:

public class DiscriminatorLengthConvention2 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                discriminatorProperty.Builder.HasMaxLength(24);
            }
        }
    }
}

Después de compilar el modelo con esta nueva convención, encontramos que la longitud del discriminador ahora está configurada correctamente aunque se haya personalizado:

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

Podemos ir un paso más allá y configurar la longitud máxima para que sea la longitud del valor discriminador más largo:

public class DiscriminatorLengthConvention3 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                var maxDiscriminatorValueLength =
                    entityType.GetDerivedTypesInclusive().Select(e => ((string)e.GetDiscriminatorValue()!).Length).Max();

                discriminatorProperty.Builder.HasMaxLength(maxDiscriminatorValueLength);
            }
        }
    }
}

Ahora la longitud máxima de la columna discriminadora es 8, que es la longitud de "Destacado", el valor discriminador más largo en uso.

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(8)

Ejemplo: Longitud predeterminada para todas las propiedades de cadena

Veamos otro ejemplo en el que se puede usar una convención de finalización: establecer una longitud máxima predeterminada para cualquier propiedad de cadena. La convención es bastante similar al ejemplo anterior:

public class MaxStringLengthConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            property.Builder.HasMaxLength(512);
        }
    }
}

Esta convención es bastante sencilla. Busca cada propiedad de cadena en el modelo y establece su longitud máxima en 512. Al examinar la vista de depuración en las propiedades de Post, vemos que todas las propiedades de cadena ahora tienen una longitud máxima de 512.

EntityType: Post
  Properties:
    Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    AuthorId (no field, int?) Shadow FK Index
    BlogId (no field, int) Shadow Required FK Index
    Content (string) Required MaxLength(512)
    Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(512)
    PublishedOn (DateTime) Required
    Title (string) Required MaxLength(512)

Nota:

La misma configuración se puede lograr mediante la configuración previa a la convención, pero el uso de una convención permite filtrar aún más las propiedades aplicables y para que las anotaciones de datos invaliden la configuración.

Por último, antes de dejar este ejemplo, ¿qué ocurre si usamos ambos MaxStringLengthConvention y DiscriminatorLengthConvention3 al mismo tiempo? La respuesta es que depende del orden en que se agregan, ya que las convenciones de finalización del modelo se ejecutan en el orden en que se agregan. Por lo tanto, si MaxStringLengthConvention se agrega por último, se ejecutará en último lugar y establecerá la longitud máxima de la propiedad discriminadora en 512. Por lo tanto, en este caso, es mejor agregar DiscriminatorLengthConvention3 al final para que pueda invalidar la longitud máxima predeterminada solo para las propiedades discriminantes, manteniendo todas las demás propiedades de cadena con un valor de 512.

Reemplazar una convención existente

A veces, en lugar de quitar una convención existente por completo, queremos reemplazarla por una convención que hace básicamente lo mismo, pero con el comportamiento cambiado. Esto resulta útil porque la convención existente ya implementará las interfaces necesarias para activarse adecuadamente.

Ejemplo: asignación de propiedades por suscripción

EF Core asigna todas las propiedades públicas de lectura y escritura por convención. Esto podría no ser adecuado para la forma en que se definen los tipos de entidad. Para cambiar esto, podemos reemplazar por PropertyDiscoveryConvention nuestra propia implementación que no asigna ninguna propiedad a menos que se asigne explícitamente en OnModelCreating o se marque con un nuevo atributo denominado Persist:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class PersistAttribute : Attribute
{
}

Esta es la nueva convención:

public class AttributeBasedPropertyDiscoveryConvention : PropertyDiscoveryConvention
{
    public AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
        : base(dependencies)
    {
    }

    public override void ProcessEntityTypeAdded(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionContext<IConventionEntityTypeBuilder> context)
        => Process(entityTypeBuilder);

    public override void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        if ((newBaseType == null
             || oldBaseType != null)
            && entityTypeBuilder.Metadata.BaseType == newBaseType)
        {
            Process(entityTypeBuilder);
        }
    }

    private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
    {
        foreach (var memberInfo in GetRuntimeMembers())
        {
            if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
            {
                entityTypeBuilder.Property(memberInfo);
            }
            else if (memberInfo is PropertyInfo propertyInfo
                     && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null)
            {
                entityTypeBuilder.Ignore(propertyInfo.Name);
            }
        }

        IEnumerable<MemberInfo> GetRuntimeMembers()
        {
            var clrType = entityTypeBuilder.Metadata.ClrType;

            foreach (var property in clrType.GetRuntimeProperties()
                         .Where(p => p.GetMethod != null && !p.GetMethod.IsStatic))
            {
                yield return property;
            }

            foreach (var property in clrType.GetRuntimeFields())
            {
                yield return property;
            }
        }
    }
}

Sugerencia

Al reemplazar una convención integrada, la nueva implementación de convención debe heredar de la clase de convención existente. Tenga en cuenta que algunas convenciones tienen implementaciones relacionales o específicas del proveedor, en cuyo caso la nueva implementación de convención debe heredar de la clase de convención más específica para el proveedor de base de datos en uso.

A continuación, la convención se registra mediante el Replace método en ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Replace<PropertyDiscoveryConvention>(
        serviceProvider => new AttributeBasedPropertyDiscoveryConvention(
            serviceProvider.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
}

Sugerencia

Este es un caso en el que la convención existente tiene dependencias, representadas por el ProviderConventionSetBuilderDependencies objeto de dependencia. Estos se obtienen del proveedor de servicios interno mediante GetRequiredService y se pasan al constructor de convención.

Tenga en cuenta que esta convención permite asignar campos (además de las propiedades) siempre que estén marcados con [Persist]. Esto significa que podemos usar campos privados como claves ocultas en el modelo.

Por ejemplo, considere los siguientes tipos de entidad:

public class LaundryBasket
{
    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    public bool IsClean { get; set; }

    public List<Garment> Garments { get; } = new();
}

public class Garment
{
    public Garment(string name, string color)
    {
        Name = name;
        Color = color;
    }

    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    [Persist]
    public string Name { get; }

    [Persist]
    public string Color { get; }

    public bool IsClean { get; set; }

    public LaundryBasket? Basket { get; set; }
}

El modelo creado a partir de estos tipos de entidad es:

Model:
  EntityType: Garment
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Basket_id (no field, int?) Shadow FK Index
      Color (string) Required
      Name (string) Required
      TenantId (int) Required
    Navigations:
      Basket (LaundryBasket) ToPrincipal LaundryBasket Inverse: Garments
    Keys:
      _id PK
    Foreign keys:
      Garment {'Basket_id'} -> LaundryBasket {'_id'} ToDependent: Garments ToPrincipal: Basket ClientSetNull
    Indexes:
      Basket_id
  EntityType: LaundryBasket
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      TenantId (int) Required
    Navigations:
      Garments (List<Garment>) Collection ToDependent Garment Inverse: Basket
    Keys:
      _id PK

Normalmente, IsClean se habría asignado, pero dado que no está marcado con [Persist], se considera ahora como una propiedad sin asignar.

Sugerencia

Esta convención no se pudo implementar como una convención de finalización del modelo porque hay convenciones de finalización de modelos existentes que deben ejecutarse después de asignar la propiedad para configurarla aún más.

Consideraciones sobre la implementación de convenciones

EF Core realiza un seguimiento de cómo se realizó cada parte de la configuración. Esto se representa mediante la ConfigurationSource enumeración . Los distintos tipos de configuración son:

  • Explicit: el elemento de modelo se configuró explícitamente en OnModelCreating
  • DataAnnotation: El elemento de modelo se configuró mediante un atributo de mapeo (también conocido como anotación de datos) en el Tipo CLR.
  • Convention: el elemento de modelo se configuró mediante una convención de creación de modelos.

Las convenciones nunca deben invalidar la configuración marcada como DataAnnotation o Explicit. Esto se logra mediante un generador de convenciones, por ejemplo, el IConventionPropertyBuilder, que se obtiene de la propiedad Builder. Por ejemplo:

property.Builder.HasMaxLength(512);

Al llamar HasMaxLength en el generador de convenciones solo se establecerá la longitud máxima si aún no ha sido configurada mediante un atributo de mapeo o en OnModelCreating.

Los métodos del generador como este también tienen un segundo parámetro: fromDataAnnotation. Establézcalo en true si la convención efectúa la configuración de parte de un atributo de mapeo. Por ejemplo:

property.Builder.HasMaxLength(512, fromDataAnnotation: true);

Esto configura ConfigurationSource en DataAnnotation, lo que significa que el valor ahora se puede sobrescribir mediante asignación explícita en OnModelCreating, pero no mediante convenciones de atributos que no implican asignación.

Si no se puede invalidar la configuración actual, el método devolverá null, esto debe tener en cuenta si necesita realizar más configuración:

property.Builder.HasMaxLength(512)?.IsUnicode(false);

Tenga en cuenta que si la configuración unicode no se puede invalidar, se seguirá estableciendo la longitud máxima. En caso de que necesite configurar las facetas solo cuando ambas llamadas se realicen correctamente, puede comprobar esto de forma preventiva llamando a CanSetMaxLength y CanSetIsUnicode:

public class MaxStringLengthNonUnicodeConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            var propertyBuilder = property.Builder;
            if (propertyBuilder.CanSetMaxLength(512)
                && propertyBuilder.CanSetIsUnicode(false))
            {
                propertyBuilder.HasMaxLength(512)!.IsUnicode(false);
            }
        }
    }
}

Aquí podemos estar seguros de que la llamada a HasMaxLength no devolverá null. Todavía se recomienda usar la instancia del generador devuelta de HasMaxLength , ya que podría ser diferente de propertyBuilder.

Nota:

Otras convenciones no se desencadenan inmediatamente después de que una convención realice un cambio, se retrasan hasta que todas las convenciones hayan terminado de procesar el cambio actual.

IConventionContext

Todos los métodos de convención también tienen un IConventionContext<TMetadata> parámetro . Proporciona métodos que podrían ser útiles en algunos casos específicos.

Ejemplo: Convención NotMappedAttribute

Esta convención busca NotMappedAttribute un tipo que se agrega al modelo e intenta quitar ese tipo de entidad del modelo. Pero si el tipo de entidad se quita del modelo, no es necesario ejecutar ninguna otra convenciones que implementen ProcessEntityTypeAdded . Esto se puede lograr llamando a StopProcessing():

public virtual void ProcessEntityTypeAdded(
    IConventionEntityTypeBuilder entityTypeBuilder,
    IConventionContext<IConventionEntityTypeBuilder> context)
{
    var type = entityTypeBuilder.Metadata.ClrType;
    if (!Attribute.IsDefined(type, typeof(NotMappedAttribute), inherit: true))
    {
        return;
    }

    if (entityTypeBuilder.ModelBuilder.Ignore(entityTypeBuilder.Metadata.Name, fromDataAnnotation: true) != null)
    {
        context.StopProcessing();
    }
}

IConventionModel

Cada objeto de generador pasado a la convención expone una Metadata propiedad que proporciona acceso de bajo nivel a los objetos que componen el modelo. En particular, hay métodos que permiten iterar sobre objetos específicos en el modelo y aplicarles una configuración común, como se muestra en Ejemplo: Longitud predeterminada para todas las propiedades de cadena. Esta API es similar a IMutableModel mostrada en configuración masiva.

Precaución

Se recomienda realizar siempre la configuración llamando a los métodos del generador que se expone como propiedad Builder, ya que los generadores comprueban si la configuración dada invalidaría algo que ya se especificó mediante API fluida o anotaciones de datos.

Cuándo usar cada enfoque para la configuración masiva

Use la API de metadatos cuando:

  • La configuración debe aplicarse en un momento determinado y no reaccionar a cambios posteriores en el modelo.
  • La velocidad de creación del modelo es muy importante. La API de metadatos tiene menos comprobaciones de seguridad y, por tanto, puede ser ligeramente más rápida que otros enfoques, pero el uso de un modelo compilado produciría incluso mejores tiempos de inicio.

Use la configuración del modelo anterior a la convención cuando:

  • La condición de aplicabilidad es sencilla, ya que solo depende del tipo.
  • La configuración debe aplicarse en cualquier momento, se agrega una propiedad del tipo especificado en el modelo y invalida las convenciones y anotaciones de datos.

Utilice Convenciones de Finalización cuando:

  • La condición de aplicabilidad es compleja.
  • La configuración no debe invalidar lo especificado por anotaciones de datos.

Use Convenciones Interactivas cuando:

  • Varias convenciones dependen entre sí. Las convenciones de finalización se ejecutan en el orden en que se agregaron y, por lo tanto, no pueden reaccionar a los cambios realizados por las convenciones de finalización posteriores.
  • La lógica se comparte entre varios contextos. Las convenciones interactivas son más seguras que otros enfoques.