EF Core :  Configuração do modelo pré-convenção


  Neste artigo veremos como fazer uma configuração do modelo pré-convenção no EF Core

No EF Core a configuração do modelo pré-convenção refere-se à configuração de entidades e propriedades antes que o modelo seja criado pelo EF Core.


Isso pode ser feito usando vários métodos, incluindo anotações de dados, API fluente e o método OnModelCreating na classe DbContext.

Nas versões anteriores do EF Core, o mapeamento explícito era necessário para cada propriedade quando esta diferia do padrão, incluindo detalhes como o comprimento máximo de cadeias de caracteres e precisão decimal, bem como a conversão de valor para o tipo de propriedade.

Isso significava que:

- A configuração do model builder para cada propriedade era necessária;
- Um atributo de mapeamento devia ser colocado em cada propriedade
- A iteração explícita sobre todas as propriedades de todos os tipos de entidade e a utilização das APIs de metadados de baixo nível eram realizadas durante a construção do modelo, que era propensa a erros e difícil de fazer com precisão, pois a lista de tipos de entidade e propriedades mapeadas poderia estar sujeita a alterações.

A partir do EF Core 6.0 esse processo foi simplificado permitindo que a configuração de mapeamento seja especificada uma vez para um determinado tipo e aplicada automaticamente a todas as propriedades desse tipo no modelo. que será então usado pelas convenções de construção de modelo.

Desta forma , agora o EF Core permite que a configuração de mapeamento seja especificada uma vez para um determinado tipo da 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", pois configura aspectos do modelo antes que as convenções de construção do modelo possam ser executadas; essa configuração é aplicada substituindo ConfigureConventions no tipo derivado de DbContext.

Para ilustrar como este recurso funciona vamos supor que temos um modelo definido com duas entidades:

public class Autor
{
  
public int Id { get; set; }
  
public string? Nome { get; set; }
  
public string? Sobrenome { get; set; }
  
public ICollection<Livro> Livros { get; } = new List<Livro>();
}

public class Livro
{
  
public int Id { get; set; }
  
public string? Titulo { get; set; }
  
public DateTime PublicadoEm { get; set; }
  
public bool EstaDisponivel { get; set; }
  
public decimal Preco { get; set; }
  
public Autor? Autor { get; set; }
}

Agora vamos supor que desejamos configurar as propriedades PublicadoEm, EstaDisponivel e Preco conforme abaixo:

  1. Os tipos strings - deverá ser definidos com um tamanho máximo de 200;
  2. Os tipos DateTime - deverão ser convertidos para um tipo long ou BIGINT;
  3. Os tipos booleanos - deverão ser convertidos para um INT (com valores 0 e 1);
  4. Os tipos decimal - Deverão ser definidos com 10 digitos e 2 casas decimais;

Para aplicar estas configurações podemos agora usar o método ConfigureConventions(ModelConfigurationBuilder configurationBuilder) da classe de contexto conforme mostra o exemplo a seguir:

...

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
   configurationBuilder
      .Properties<
string>()
      .AreUnicode(
false)
      .HaveMaxLength(200);

    configurationBuilder
      .Properties<DateTime>()
      .HaveConversion<
long>();

    configurationBuilder
       .Properties<
bool>()
       .HaveConversion<BoolToZeroOneConverter<
int>>();    

     configurationBuilder
       .Properties<
decimal>()
       .HavePrecision(10, 3);
}

Após definir estas configurações ao aplicar o Migrations podemos conferir no script gerado a configuração aplicada:

migrationBuilder.CreateTable(
    name:
"Autores",
    columns: table =>
new
    {
       Id = table.Column<
int>(type: "int", nullable: false)
            .Annotation(
"SqlServer:Identity", "1, 1"),
       Nome = table.Column<
string>(type: "varchar(200)", unicode: false, maxLength: 200, nullable: true),
       Sobrenome = table.Column<
string>(type: "varchar(200)", unicode: false, maxLength: 200, nullable: true)
    },
    constraints: table =>
    {
       table.PrimaryKey(
"PK_Autores", x => x.Id);
    });

   migrationBuilder.CreateTable(
     name:
"Livros",
     columns: table =>
new
    {
         Id = table.Column<
int>(type: "int", nullable: false)
             .Annotation(
"SqlServer:Identity", "1, 1"),
         Titulo = table.Column<
string>(type: "varchar(200)", unicode: false, maxLength: 200, nullable: true),
         PublicadoEm = table.Column<
long>(type: "bigint", nullable: false),
         EstaDisponivel = table.Column<
int>(type: "int", nullable: false),
         Preco = table.Column<
decimal>(type: "decimal(10,3)", precision: 10, scale: 3, nullable: false
),
         AutorId = table.Column<
int>(type: "int", nullable: true)
    },
    constraints: table =>
    {
         table.PrimaryKey(
"PK_Livros", x => x.Id);
         table.ForeignKey(
               name:
"FK_Livros_Autores_AutorId",
               column: x => x.AutorId,
               principalTable:
"Autores",
               principalColumn:
"Id");
    });

Assim essas configurações serão aplicadas em todo o seu projeto para cada Migration aplicada e devem ser usadas quando um aspecto precisa ser configurado da mesma forma em vários tipos de entidade.

Naturalmente se você desejar sobrescrever as configurações definidas poderá usar o código padrão no método OnModelCreating:

  modelBuilder.Entity<Livro>(builder =>
    {
        builder
            .Property(x => x.Preco)
            .HasPrecision(12, 2);
    });

Muitos aspectos não podem ainda serem configurados com esta abordagem, e provavelmente este recurso será expandido em versões futuras.

Essa configuração é realizada antes de um modelo ser criado. Se houver algum conflito ao aplicá-lo, o rastreamento de pilha de exceção não conterá o método ConfigureConventions, portanto, pode ser mais difícil encontrar a causa.

Em outro artigo vamos continuar tratando das convenções mostrando o novo recurso do EF Core chamado Model building conventions onde podemos remover, incluir e definir configurações personalizadas.

E estamos conversados...

"Tendo sido, pois, justificados pela fé, temos paz com Deus, por nosso Senhor Jesus Cristo;"
Romanos 5:1

Referências:


José Carlos Macoratti