Compartir a través de


Configuración masiva de modelos

Cuando sea 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 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 ejemplo siguiente, el modelo contiene un tipo de valor Currency personalizado:

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 propiedades Currency se configuraron como navegación por 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 mediante una llamada a DelayConventions() y, posteriormente, eliminar el objeto devuelto o marcar la propiedad CLR como se omite 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 anterior a la convención

EF Core permite especificar la configuración de asignación una vez para un tipo CLR determinado; esa configuración se aplica a todas las propiedades de ese tipo en el modelo a medida que se detectan. Esto se denomina "configuración del modelo anterior a la convención", ya que configura aspectos del modelo antes de permitir que se ejecuten las convenciones de creación de modelos. Esta configuración se aplica al invalidar 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 distinto a NULL
  5. Tipo exacto

Importante

La configuración anterior 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.

Omisión de tipos

La configuración anterior 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 predeterminados

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 a DefaultTypeMapping para agregar o invalidar una asignación de tipos de proveedor:

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

Limitaciones de la configuración anterior 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 método ConfigureConventions, por lo que podría ser más difícil encontrar la causa.

Convenciones

Nota:

Las convenciones de creación de modelos personalizados se introdujeron en EF Core 7.0.

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 actualizado el modelo a medida que se realiza una 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 IForeignKeyAddedConvention y IKeyAddedConvention se desencadenará 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 puede usar en su lugar para especificar fácilmente una 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 Discriminator

La estrategia de asignación de herencia de tabla por jerarquía requiere una columna discriminatoria para especificar qué tipo se representa en cualquier fila determinada. De forma predeterminada, EF usa una columna de cadena sin enlazar 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 actualizado el modelo a medida que se realiza una 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 IForeignKeyAddedConvention y IKeyAddedConvention se desencadenará 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 primer intento de implementar la convención de longitud discriminadora:

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 Discriminator de cadena 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 crear el modelo y examinar el tipo de entidad Post, se muestra que esta propiedad ha funcionado; la propiedad Discriminator ahora está configurada para con una longitud máxima de 24:

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

¿Pero qué ocurre si ahora configuramos explícitamente una propiedad Discriminator diferente? 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 de Discriminator ya no está configurada.

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

Esto se debe a que la propiedad discriminador que configuramos en nuestra convención se quitó posteriormente 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. Mucho tiempo, no importa el aspecto del modelo mientras se compila, siempre y cuando 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 cerca del 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á actualizado en cualquier punto de la ejecución del método OnModelCreating. Normalmente, una convención de finalización de modelos recorre en iteración todos los elementos del modelo que configuran los elementos del modelo a medida que va. 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)

Vamos a 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

Echemos un vistazo a otro ejemplo en el que se puede usar una convención de finalización: mediante el establecimiento de 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 anterior 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 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 Discriminator en 512. Por lo tanto, en este caso, es mejor agregar DiscriminatorLengthConvention3 el último para que pueda invalidar la longitud máxima predeterminada para solo las propiedades Discriminator, al tiempo que deja todas las demás propiedades de cadena como 512.

Eliminación de 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 que necesita para que se desencadene correctamente.

Ejemplo: Asignación de propiedades de participació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 método Replace 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 objeto de dependencia ProviderConventionSetBuilderDependencies. 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 tipos de entidad siguientes:

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, se habrían asignado IsClean, pero dado que no está marcado con [Persist], ahora se trata 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 enumeración ConfigurationSource. 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 asignación (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 invalidan la configuración marcada como DataAnnotation o Explicit. Esto se consigue utilizando un constructor de convenciones, por ejemplo, el IConventionPropertyBuilder, que se obtiene de la propiedad Builder. Por ejemplo:

property.Builder.HasMaxLength(512);

Al llamar a HasMaxLength en el generador de convenciones solo se establecerá la longitud máxima si aún no se configuró mediante un atributo de asignación 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 realiza la configuración en nombre de un atributo de asignación. Por ejemplo:

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

Esto establece ConfigurationSource en DataAnnotation, lo que significa que el valor ahora se puede invalidar mediante la asignación explícita en OnModelCreating, pero no mediante convenciones de atributo que no son de 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 parámetro IConventionContext<TMetadata>. Proporciona métodos que podrían ser útiles en algunos casos específicos.

Ejemplo: Convención NotMappedAttribute

Esta convención busca NotMappedAttribute en 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 propiedad Metadata que proporciona 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, tal como se muestra en Ejemplo: Longitud predeterminada para todas las propiedades de cadena. Esta API es similar a IMutableModel, que se muestra en Configuración masiva.

Precaución

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

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

Utilice la API de metadatos en los siguientes casos:

  • 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.

Utilice 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 punto en el que se agregue una propiedad del tipo dado en el modelo e invalide las anotaciones de datos y las convenciones.

Use las convenciones de finalización en los siguientes casos:

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

Use las convenciones interactivas en los siguientes casos:

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