Partilhar via


Configuração em massa do modelo

Quando um aspeto precisa ser configurado da mesma maneira em vários tipos de entidade, as técnicas a seguir permitem reduzir a duplicação de código e consolidar a lógica.

Veja o projeto de exemplo completo contendo os trechos de código apresentados abaixo.

Configuração em massa no OnModelCreating

Cada objeto de construtor retornado de ModelBuilder expõe uma propriedade Model ou Metadata que fornece acesso de baixo nível aos objetos que compõem o modelo. Particularmente, existem métodos que permitem iterar sobre objetos específicos no modelo e aplicar uma configuração comum a esses objetos.

No exemplo a seguir, o modelo contém um tipo Currencyde valor personalizado:

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

    public decimal Amount { get; }

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

Propriedades desse tipo não são descobertas por padrão, pois o provedor EF atual não sabe como mapeá-lo para um tipo de banco de dados. Este trecho de OnModelCreating adiciona todas as propriedades do tipo Currency e configura um conversor de valor para um tipo suportado - 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))
    {
    }
}

Desvantagens da API de metadados

  • Ao contrário da API Fluent, cada modificação no modelo precisa ser feita explicitamente. Por exemplo, se algumas das propriedades foram configuradas como navegações por uma convenção, você precisará primeiro remover a navegação que faz referência à propriedade CLR antes de Currency adicionar uma propriedade de tipo de entidade para ela. #9117 vai melhorar isso.
  • As convenções são executadas após cada alteração. Se você remover uma navegação descoberta por uma convenção, a convenção será executada novamente e poderá adicioná-la novamente. Para evitar que isso aconteça, você precisa atrasar as convenções até que a propriedade seja adicionada chamando DelayConventions() e descartando posteriormente o objeto retornado ou marcar a propriedade CLR como ignorada usando AddIgnored.
  • Os tipos de entidade podem ser adicionados depois que essa iteração acontece e a configuração não será aplicada a eles. Isso geralmente pode ser evitado colocando esse código no final do OnModelCreating, mas se você tiver dois conjuntos interdependentes de configurações, pode não haver uma ordem que permita que eles sejam aplicados de forma consistente.

Configuração pré-convenção

O EF Core permite que a configuração de mapeamento seja especificada uma vez para um determinado tipo de CLR; Essa configuração é então aplicada a todas as propriedades desse tipo no modelo à medida que são descobertas. Isso é chamado de "configuração de modelo pré-convenção", uma vez que configura aspetos do modelo antes que as convenções de construção de modelo sejam permitidas para serem executadas. Tal configuração é aplicada substituindo ConfigureConventions no tipo derivado de DbContext.

Este exemplo mostra como configurar todas as propriedades do tipo Currency para ter um conversor de valor:

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

E este exemplo mostra como configurar algumas facetas em todas as propriedades do tipo string:

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

Observação

O tipo especificado numa chamada de ConfigureConventions pode ser um tipo base, uma interface ou uma definição de tipo genérica. Todas as configurações correspondentes serão aplicadas em ordem a partir das menos específicas:

  1. Interfaz
  2. Tipo de base
  3. Definição genérica de tipo
  4. Tipo de valor não anulável
  5. Tipo exato

Importante

A configuração pré-convenção é equivalente à configuração explícita que é aplicada assim que um objeto correspondente é adicionado ao modelo. Ele substituirá todas as convenções e anotações de dados. Por exemplo, com a configuração acima, todas as propriedades de chave estrangeira de cadeia de caracteres serão criadas como não-unicode com MaxLength de 1024, mesmo quando isso não corresponder à chave principal.

Ignorando tipos

A configuração pré-convenção também permite ignorar um tipo e impedir que ele seja descoberto por convenções como um tipo de entidade ou como uma propriedade em um tipo de entidade:

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

Mapeamento de tipo padrão

Geralmente, o EF é capaz de traduzir consultas com constantes de um tipo que não é suportado pelo provedor, desde que você tenha especificado um conversor de valor para uma propriedade desse tipo. No entanto, em consultas que não envolvem nenhuma propriedade desse tipo, não há como o EF encontrar o conversor de valor correto. Nesse caso, é possível chamar DefaultTypeMapping para adicionar ou substituir um mapeamento de tipo de provedor.

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

Limitações da configuração pré-convenção

  • Muitos aspetos não podem ser configurados com esta abordagem. #6787 expandirá isso para mais tipos.
  • Atualmente, a configuração é determinada apenas pelo tipo CLR. #20418 permitiria predicados personalizados.
  • Essa configuração é executada antes de um modelo ser criado. Se houver conflitos que ocorram durante a sua aplicação, o rastreamento de pilha de exceção não conterá o método ConfigureConventions, portanto, pode ser mais difícil encontrar a causa.

Convenções

As convenções de construção de modelo do EF Core são classes que contêm lógica que é acionada com base nas alterações feitas no modelo à medida que ele está sendo construído. Isso mantém o modelo up-to-date à medida que a configuração explícita é feita, os atributos de mapeamento são aplicados e outras convenções são executadas. Para participar disso, cada convenção implementa uma ou mais interfaces que determinam quando o método correspondente será acionado. Por exemplo, uma convenção que implementa IEntityTypeAddedConvention será disparada sempre que um novo tipo de entidade for adicionado ao modelo. Da mesma forma, uma convenção que implementa ambos IForeignKeyAddedConvention e IKeyAddedConvention será acionada sempre que uma chave ou uma chave estrangeira for adicionada ao modelo.

As convenções de construção de modelos são uma maneira poderosa de controlar a configuração do modelo, mas podem ser complexas e difíceis de acertar. Em muitos casos, a configuração do modelo pré-convenção pode ser usada para especificar facilmente a configuração comum de propriedades e tipos.

Adicionar uma nova convenção

Exemplo: Restringir o comprimento das propriedades discriminadoras

A estratégia de mapeamento de herança de tabela por hierarquia requer uma coluna discriminadora para indicar qual tipo é representado numa determinada linha. Por padrão, o EF usa uma coluna de cadeia de caracteres não limitada para o discriminador, o que garante que ele funcionará para qualquer comprimento de discriminador. No entanto, restringir o comprimento máximo das cadeias de caracteres discriminadoras pode tornar o armazenamento e as consultas mais eficientes. Vamos criar uma nova convenção que faça isso.

As convenções de construção do modelo EF Core são acionadas com base nas alterações feitas no modelo à medida que ele está sendo construído. Isso mantém o modelo up-to-date à medida que a configuração explícita é feita, os atributos de mapeamento são aplicados e outras convenções são executadas. Para participar disso, cada convenção implementa uma ou mais interfaces que determinam quando a convenção será acionada. Por exemplo, uma convenção que implementa IEntityTypeAddedConvention será disparada sempre que um novo tipo de entidade for adicionado ao modelo. Da mesma forma, uma convenção que implementa ambos IForeignKeyAddedConvention e IKeyAddedConvention será acionada sempre que uma chave ou uma chave estrangeira for adicionada ao modelo.

Saber quais interfaces implementar pode ser complicado, já que a configuração feita no modelo em um ponto pode ser alterada ou removida em um ponto posterior. Por exemplo, uma chave pode ser criada por convenção, mas depois substituída quando uma chave diferente é configurada explicitamente.

Vamos tornar isso um pouco mais concreto, fazendo uma primeira tentativa de implementar a convenção de comprimento discriminatório:

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 convenção implementa IEntityTypeBaseTypeChangedConvention, o que significa que será acionada sempre que a hierarquia de herança mapeada para um tipo de entidade for alterada. Em seguida, a convenção localiza e configura a propriedade de discriminador de cadeia de caracteres para a hierarquia.

Esta convenção é então utilizada através da chamada de Add em ConfigureConventions.

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

Observação

Em vez de adicionar uma instância da convenção diretamente, o método Add aceita uma factory para criar instâncias da convenção. Isso permite que a convenção use dependências do provedor de serviços interno do EF Core. Como essa convenção não tem dependências, o parâmetro do provedor de serviços é nomeado _, indicando que ele nunca é usado.

Construir o modelo e examinar o Post tipo de entidade mostra que isso funcionou - a propriedade discriminator agora está configurada para com um comprimento máximo de 24:

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

Mas o que acontece se agora configurarmos explicitamente uma propriedade discriminadora diferente? Por exemplo:

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

Observando a visualização de depuração do modelo, descobrimos que o comprimento do discriminador não está mais configurado.

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

Isso ocorre porque a propriedade discriminator que configuramos em nossa convenção foi posteriormente removida quando o discriminador personalizado foi adicionado. Poderíamos tentar corrigir isso implementando outra interface em nossa convenção para reagir às mudanças discriminadoras, mas descobrir qual interface implementar não é fácil.

Felizmente, existe uma abordagem mais fácil. Muitas vezes, não importa como o modelo se parece enquanto está sendo construído, desde que o modelo final esteja correto. Além disso, a configuração que queremos aplicar muitas vezes não precisa acionar outras convenções para reagir. Portanto, a nossa convenção pode implementar IModelFinalizingConvention. As convenções de finalização do modelo são executadas depois que todos os outros modelos de construção estão concluídos e, portanto, têm acesso ao estado quase final do modelo. Isso se opõe às convenções interativas que reagem a cada alteração de modelo e garantem que o modelo esteja up-todata em qualquer ponto da execução do OnModelCreating método. Uma convenção de finalização de modelo normalmente itera sobre todo o modelo configurando os elementos do modelo à medida que avança. Assim, neste caso, vamos encontrar cada discriminador no modelo e configurá-lo:

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

Depois de construir o modelo com esta nova convenção, descobrimos que o comprimento do discriminador agora está configurado corretamente, embora tenha sido personalizado:

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

Podemos ir um passo além e configurar o comprimento máximo para ser o comprimento do maior valor discriminador:

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

Agora, o comprimento máximo da coluna do discriminador é 8, que é o comprimento de "Destaque", o maior valor do discriminador em uso.

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

Exemplo: comprimento padrão para todas as propriedades de cadeia de caracteres

Vejamos outro exemplo em que uma convenção de finalização pode ser usada - definindo um comprimento máximo padrão para qualquer propriedade de cadeia de caracteres. A convenção é bastante semelhante ao exemplo 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 convenção é bastante simples. Ele encontra todas as propriedades de cadeia de caracteres no modelo e define seu comprimento máximo como 512. Olhando na visualização de depuração para as propriedades do Post, vemos que todas as propriedades de cadeia de caracteres agora têm um comprimento máximo 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)

Observação

O mesmo pode ser feito pela configuração pré-convenção, mas o uso de uma convenção permite filtrar ainda mais as propriedades aplicáveis e que as Anotações de Dados substituam a configuração.

Finalmente, antes de deixarmos este exemplo, o que acontece se usarmos ao mesmo tempo os MaxStringLengthConvention e DiscriminatorLengthConvention3? A resposta é que depende da ordem em que são adicionadas, uma vez que as convenções de finalização de modelos são executadas na ordem em que são adicionadas. Portanto, se MaxStringLengthConvention for adicionado por último, ele será executado por último e definirá o comprimento máximo da propriedade discriminator para 512. Portanto, neste caso, é melhor adicionar DiscriminatorLengthConvention3 por último para que ele possa substituir o comprimento máximo predefinido apenas para propriedades discriminadoras, deixando todas as outras propriedades de texto como 512.

Substituição de uma convenção existente

Às vezes, em vez de remover completamente uma convenção existente, queremos substituí-la por uma convenção que faça basicamente a mesma coisa, mas com comportamento alterado. Isso é útil porque a convenção existente já implementará as interfaces necessárias para ser acionada de forma adequada.

Exemplo: mapeamento de propriedades de adesão

O EF Core mapeia todas as propriedades públicas de leitura-gravação por convenção. Isso pode não ser apropriado para a maneira como seus tipos de entidade são definidos. Para alterar isso, podemos substituir o PropertyDiscoveryConvention por nossa própria implementação que não mapeia nenhuma propriedade, a menos que seja explicitamente mapeada OnModelCreating ou marcada com um novo atributo chamado Persist:

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

Eis a nova convenção:

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

Sugestão

Ao substituir uma convenção interna, a implementação da nova convenção deve herdar da classe de convenção existente. Observe que algumas convenções têm implementações relacionais ou específicas do provedor, caso em que a implementação da nova convenção deve herdar da classe de convenção existente mais específica para o provedor de banco de dados em uso.

A convenção é então registada utilizando o Replace método em ConfigureConventions:

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

Sugestão

Este é um caso em que a convenção existente tem dependências, representadas pelo ProviderConventionSetBuilderDependencies objeto de dependência. Estes são obtidos do provedor de serviços interno usando GetRequiredService e passados para o construtor de convenções.

Observe que essa convenção permite que os campos sejam mapeados (além das propriedades), desde que estejam marcados com [Persist]. Isso significa que podemos usar campos privados como chaves ocultas no modelo.

Por exemplo, considere os seguintes tipos de entidade:

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

O modelo criado a partir desses tipos de entidade é:

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 teria sido mapeado, mas como não está marcado com [Persist], agora é tratado como uma propriedade não mapeada.

Sugestão

Essa convenção não pôde ser implementada como uma convenção de finalização como modelo porque há convenções de finalização de modelo já existentes que precisam ser executadas depois que a propriedade é mapeada para fazer uma configuração adicional.

Considerações sobre a implementação das convenções

O EF Core mantém o controle de como cada parte da configuração foi feita. Isso é representado pelo ConfigurationSource enum. Os diferentes tipos de configuração são:

  • Explicit: O elemento de modelo foi explicitamente configurado em OnModelCreating
  • DataAnnotation: O elemento de modelo foi configurado usando um atributo de mapeamento (também conhecido como anotação de dados) no tipo CLR
  • Convention: O elemento de modelo foi configurado por uma convenção de construção de modelo

As convenções nunca devem substituir a configuração marcada como DataAnnotation ou Explicit. Isto é conseguido usando um construtor de convenções, por exemplo, o IConventionPropertyBuilder, que é obtido a partir da Builder propriedade. Por exemplo:

property.Builder.HasMaxLength(512);

Chamar HasMaxLength no construtor de convenções só definirá o comprimento máximo caso ainda não tenha sido configurado por um atributo de mapeamento ou em OnModelCreating.

Métodos de construtor como este também têm um segundo parâmetro: fromDataAnnotation. Defina isso como true se a convenção estiver fazendo a configuração em nome de um atributo de mapeamento. Por exemplo:

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

Isso define o ConfigurationSource como DataAnnotation, o que significa que o valor agora pode ser substituído por mapeamento explícito no OnModelCreating, mas não por convenções de atributos que não são mapeamento.

Se a configuração atual não puder ser substituída, o método retornará null, isso precisa ser levado em conta se você precisar executar uma configuração adicional:

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

Observe que, se a configuração unicode não puder ser substituída, o comprimento máximo ainda será definido. No caso de você precisar configurar as facetas somente quando ambas as chamadas forem bem-sucedidas, você pode verificar isso preventivamente chamando CanSetMaxLength e 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);
            }
        }
    }
}

Aqui podemos ter certeza que a ligação para HasMaxLength não retornará null. Ainda é recomendável usar a instância do construtor retornada de HasMaxLength, pois ela pode ser diferente de propertyBuilder.

Observação

Outras convenções não são acionadas imediatamente após uma convenção fazer uma alteração, elas são adiadas até que todas as convenções tenham terminado de processar a alteração atual.

IConventionContext

Todos os métodos de convenção também têm um IConventionContext<TMetadata> parâmetro. Fornece métodos que podem ser úteis em alguns casos específicos.

Exemplo: convenção NotMappedAttribute

Esta convenção procura por NotMappedAttribute em um tipo que é adicionado ao modelo e tenta remover esse tipo de entidade do modelo. Mas se o tipo de entidade for removido do modelo, quaisquer outras convenções que implementarem ProcessEntityTypeAdded não precisarão mais ser executadas. Isso pode ser feito chamando 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 construtor passado para a convenção expõe uma Metadata propriedade que fornece um acesso de baixo nível aos objetos que compõem o modelo. Em especial, há métodos que permitem iterar sobre objetos específicos no modelo e aplicar uma configuração comum a eles, como visto em Exemplo: comprimento padrão para todas as propriedades de texto. Essa API é semelhante à IMutableModel mostrada na configuração em massa.

Atenção

É aconselhável sempre executar a configuração chamando métodos no builder exposto como a propriedade Builder, porque o builder verifica se a configuração dada substituiria algo que já foi especificado usando API Fluente ou Anotações de Dados.

Quando usar cada abordagem para configuração em massa

Use a API de metadados quando:

  • A configuração precisa ser aplicada em um determinado momento e não reagir a alterações posteriores no modelo.
  • A velocidade de construção do modelo é muito importante. A API de metadados tem menos verificações de segurança e, portanto, pode ser um pouco mais rápida do que outras abordagens, no entanto, usar um modelo compilado produziria tempos de inicialização ainda melhores.

Utilize a configuração do modelo pré-convenção quando:

  • A condição de aplicabilidade é simples, pois depende apenas do tipo.
  • A configuração precisa ser aplicada em qualquer ponto em que uma propriedade do tipo determinado é adicionada no modelo e substitui as anotações e convenções de dados

Utilize Convenções de Finalização quando:

  • A condição de aplicabilidade é complexa.
  • A configuração não deve substituir o especificado pelas Anotações de Dados.

Use as Convenções Interativas quando:

  • Várias convenções dependem umas das outras. As convenções de finalização são executadas na ordem em que foram adicionadas e, portanto, não podem reagir às alterações feitas por convenções de finalização que ocorrem mais tarde.
  • A lógica é partilhada entre vários contextos. As convenções interativas são mais seguras do que outras abordagens.