Compartilhar via


Configuração em massa do modelo

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

Consulte o projeto de exemplo completo que contém os snippets de código apresentados abaixo.

Configuração em massa em OnModelCreating

Cada objeto de construtor retornado de ModelBuilder expõe uma Model ou Metadata propriedade que fornece acesso de nível baixo 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.

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

As 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 snippet de OnModelCreating adiciona todas as propriedades do tipo Currency e configura um conversor de valor a um tipo com suporte : 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 fluente, todas as modificações no modelo precisam ser feitas explicitamente. Por exemplo, se algumas das Currency propriedades foram configuradas como navegações por uma convenção, você precisa primeiro remover a navegação que referencia a propriedade do CLR, antes de associar uma propriedade do tipo de entidade a ela. O nº 9117 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 de volta. Para evitar que isso aconteça, você precisará 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 acontecer e a configuração não será aplicada a eles. Isso geralmente pode ser evitado colocando esse código no final, OnModelCreatingmas se você tiver dois conjuntos interdependentes de configurações, talvez não haja uma ordem que permita que elas sejam aplicadas de forma consistente.

Configuração de pré-convenção

O EF Core permite que a configuração de mapeamento seja especificada uma vez para um determinado tipo CLR; essa configuração é então aplicada a todas as propriedades desse tipo no modelo conforme elas são descobertas. Isso é chamado de "configuração de modelo de pré-convenção", pois configura aspectos do modelo antes que as convenções de criação de modelo possam ser executadas. Esta configuração é aplicada ao sobrescrever 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 em uma chamada de ConfigureConventions pode ser um tipo base, uma interface ou uma definição de tipo genérico. Todas as configurações correspondentes serão aplicadas na ordem do menos ao mais específico.

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

Importante

A configuração de 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 string serão criadas como não-unicode com MaxLength de 1024, mesmo quando não corresponderem à chave principal.

Ignorando tipos

A configuração de 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 tem suporte 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é-evento

  • Muitos aspectos não podem ser configurados com essa abordagem. O nº 6787 expandirá isso para mais tipos.
  • Atualmente, a configuração só é determinada pelo tipo CLR. O nº 20418 permitiria predicados personalizados.
  • Essa configuração é executada antes de um modelo ser criado. Se houver conflitos ao aplicá-lo, o rastreamento de pilha de exceção não conterá o método ConfigureConventions, o que pode tornar mais difícil encontrar a causa.

Convenções

As convenções de criação de modelo do EF Core são classes que contêm lógica que é disparada com base em alterações feitas no modelo conforme ele está sendo criado. Isso mantém o modelo up-to-date conforme 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á disparado. 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á disparada sempre que uma chave ou uma chave estrangeira for adicionada ao modelo.

As convenções de criaçã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 de modelo pré-convenção pode ser usada para facilmente especificar a configuração comum de propriedades e tipos.

Adicionando uma nova convenção

Exemplo: restringir o comprimento das propriedades discriminatórias

A estratégia de mapeamento de herança tabela por hierarquia requer uma coluna discriminatória para especificar qual tipo é representado em qualquer linha específica. Por padrão, o EF usa uma coluna de cadeia de caracteres não limitada para o discriminador, o que garante que ela funcione para qualquer comprimento de discriminador. No entanto, restringir o comprimento máximo de cadeias de caracteres discriminatórias pode tornar o armazenamento e as consultas mais eficientes. Vamos criar uma nova convenção que fará isso.

As convenções de criação de modelo do EF Core são acionadas com base em alterações feitas no modelo à medida que ele é construído. Isso mantém o modelo up-to-date conforme 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á disparada. 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á disparada sempre que uma chave ou uma chave estrangeira for adicionada ao modelo.

Saber quais interfaces implementar podem ser complicadas, uma vez que a configuração feita para o 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 do 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);
        }
    }
}

Essa convenção implementa IEntityTypeBaseTypeChangedConvention, o que significa que ela será disparada 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 discriminatória da cadeia de caracteres para a hierarquia.

Esta convenção é então usada chamando 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 Add método aceita uma fábrica 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.

Compilar o modelo e examinar o Post tipo de entidade mostra que isso funcionou - a propriedade discriminatória agora está configurada 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 discriminatória diferente? Por exemplo:

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

Examinando a exibição de depuração do modelo, descobrimos que o comprimento do discriminador não está mais definido.

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

Isso ocorre porque a propriedade discriminatória 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 discriminatórias, mas descobrir qual interface implementar não é fácil.

Felizmente, há uma abordagem mais fácil. Na maioria das vezes, não importa a aparência do modelo enquanto ele está sendo criado, desde que o modelo final esteja correto. Além disso, a configuração que desejamos aplicar geralmente não precisa disparar outras convenções para reagir. Portanto, nossa convenção pode implementar IModelFinalizingConvention. As convenções de finalização de modelo são executadas após a conclusão da construção de todos os outros modelos e, portanto, têm acesso ao estado quase final do modelo. Isso se opõe a convenções interativas que reagem a cada alteração de modelo e garantem que o modelo seja up-to-date em qualquer ponto da execução do OnModelCreating método. Uma convenção de finalização de modelo normalmente percorre o modelo inteiro, configurando os elementos do modelo conforme o processo avança. Portanto, nesse caso, encontraremos todos os discriminatórios no modelo e o 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);
            }
        }
    }
}

Depois de compilar o modelo com essa nova convenção, descobrimos que o comprimento discriminatório agora está configurado corretamente, embora tenha sido personalizado:

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

Podemos ir mais longe e configurar o comprimento máximo para ter o comprimento do valor discriminatório mais longo:

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 discriminatória é 8, que é o comprimento de "Destaque", o valor discriminatório mais longo em uso.

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

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

Vamos examinar 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 é muito simples. Ele localiza cada propriedade de cadeia de caracteres no modelo e define seu comprimento máximo como 512. Olhando no modo de exibição de depuração para as propriedades Post, vemos que todas as propriedades da 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 de pré-convenção, mas o uso de uma convenção permite filtrar ainda mais as propriedades aplicáveis e as Anotações de Dados para substituir a configuração.

Por fim, antes de deixarmos este exemplo, o que acontecerá se usarmos o MaxStringLengthConvention e DiscriminatorLengthConvention3 ao mesmo tempo? A resposta é que ela depende de qual ordem elas são adicionadas, uma vez que as convenções de finalização de modelo 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 discriminatória como 512. Portanto, nesse caso, é melhor adicionar DiscriminatorLengthConvention3 por último para que ele possa substituir o comprimento máximo padrão para apenas propriedades discriminatórias, deixando todas as outras propriedades de cadeia de caracteres como 512.

Substituindo uma convenção existente

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

Exemplo: mapeamento de propriedades de participação voluntária

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 os tipos de entidade são definidos. Para alterar isso, podemos substituir por PropertyDiscoveryConvention nossa própria implementação que não mapeie nenhuma propriedade, a menos que ela seja explicitamente mapeada OnModelCreating ou marcada por um novo atributo chamado Persist:

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

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

Dica

Ao substituir uma convenção interna, a nova implementação de 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; nesse caso, a nova implementação de 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 registrada usando o Replace método em ConfigureConventions:

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

Dica

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

Observe que essa convenção permite que os campos sejam mapeados (além das propriedades), desde que sejam 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 [Persist], agora é tratado como uma propriedade não mapeada.

Dica

Essa convenção não pôde ser implementada como uma convenção de finalização de modelo porque existem convenções de finalização de modelo existentes que precisam ser executadas depois que a propriedade é mapeada para configurá-la ainda mais.

Considerações de implementação de convenções

O EF Core controla como cada parte da configuração foi feita. Isso é representado pela ConfigurationSource enumeração. Os diferentes tipos de configuração são:

  • Explicit: o elemento de modelo foi configurado explicitamente 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 criação de modelo

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

property.Builder.HasMaxLength(512);

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

Métodos do 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 para DataAnnotation, o que significa que o valor agora pode ser substituído por mapeamento explícito em OnModelCreating, mas não por convenções de atributos não mapeados.

Se a configuração atual não puder ser substituída, o método retornará null, isso precisará ser contabilizado se você precisar executar outra configuração:

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. Caso precise configurar as facetas somente quando ambas as chamadas forem bem-sucedidas, você poderá 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 de que a chamada 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 disparadas imediatamente depois que uma convenção realiza uma alteração; seu processamento é adiado 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. Ele fornece métodos que podem ser úteis em alguns casos específicos.

Exemplo: convenção NotMappedAttribute

Essa convenção procura por NotMappedAttribute um tipo que é adicionado ao modelo e tenta remover esse tipo de entidade do modelo. No entanto, se o tipo de entidade for removido do modelo, outras convenções que implementam 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 do 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 a configuração padrão a eles, como visto em Exemplo: comprimento padrão para todas as propriedades de cadeia de caracteres. Esta API é semelhante à IMutableModel, conforme mostrado em configuração em lote.

Cuidado

É recomendável sempre realizar a configuração chamando métodos no builder exposto como a propriedade Builder, pois os builders verificam se a configuração fornecida substituiria algo que já foi especificado usando Fluent API ou Data Annotations.

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.

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

  • A condição de aplicabilidade é simples, pois depende apenas do tipo.
  • A configuração precisa ser aplicada a qualquer momento em que uma propriedade do tipo determinado é adicionada no modelo e substitui 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 que é especificado por Anotações de Dados.

Use 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 posteriores.
  • A lógica é compartilhada entre vários contextos. Convenções interativas são mais seguras do que outras abordagens.