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:
- Interfaz
- Tipo base
- Definición de tipo genérico
- Tipo de valor distinto a NULL
- 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 enOnModelCreating
DataAnnotation
: el elemento de modelo se configuró mediante un atributo de asignación (también conocido como anotación de datos) en el tipo CLRConvention
: 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.