Breaking changes - EF Core 3.1

16 minuto(s) de leitura - December 03, 2019

01

Fala pessoal, tudo bem?! 💚

Veja como fazer convenções de nomenclatura SnakeCase de forma fácil para o Entity Framework Core 3.1

01

FYI:
Este artigo é uma versão atualizada de: https://ralms.io/dica/snakecase/
O objetivo maior deste artigo é mostrar as alterações na API de metadados do Entity Framework Core 3.x.

01

Veja esse artigo!

Quer ver um resumidão sobre Naming Conventions?
acesse esse link: https://ralms.io/dica/snakecase/

O que me levou a escrever esse artigo?

Não quero me prolongar aqui, dado que já escrevi um artigo falando sobre minha real necessidade de utilizar conversões de nomenclaturas, então para não ser redundante acessem o link acima e leiam o primeiro artigo que escrevi.

O que mudou, Oops, aliás o que quebrou?

Se observarmos semver e olhar para o significado de MAJOR, podemos dizer quer foi bem aplicado aqui, mas não pode existir uma quebra sem um concerto, néh verdade!

Os métodos de extensão específicos do provedor sofreram alterações, nas versões anteriores ao EF 3.0, acessavamos diretamente as propriedades, agora os acessos para algumas dessas propriedades são por métodos, para alguns pode até parecer que ficou mais complicado, mas eu defendo esse tipo de abordagem, em não expor suas propriedades onde as mesmas devem ser acessadas apenas por métodos ou por um construtor, que não é o caso aqui.

Então fiz um DE-PARA aqui do artigo anterior e o que mudou.


Até o EF 2.2 (old)

Tinhamos o seguinte comportamento para acessar e alterar os metadados.

public static void ToSnakeNames(this ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        entity.Relational().TableName = entity.Relational().TableName.ToSnakeCase();

        foreach (var property in entity.GetProperties())
        {
            property.Relational().ColumnName = property
                .Relational()
                .ColumnName
                .ToSnakeCase();
        }

        foreach (var key in entity.GetKeys())
        {
            key.Relational().Name = key.Relational().Name.ToSnakeCase(); 
        }

        foreach (var key in entity.GetForeignKeys())
        {
            key.Relational().Name = key.Relational().Name.ToSnakeCase();
        }

        foreach (var index in entity.GetIndexes())
        {
            index.Relational().Name = index.Relational().Name.ToSnakeCase();
        }
    }
}

private static string ToSnakeCase(this string name)
{
    return string.IsNullOrWhiteSpace(name)
        ? name
        : Regex.Replace(
            name, 
            @"([a-z0-9])([A-Z])", 
            "$1_$2", 
            RegexOptions.Compiled,
            TimeSpan.FromSeconds(0.2)).ToLower(); 
}

Usando o EF 3.X (new)

Agora o comportamento para acessar os metadados foram alterados.

public static void ToSnakeNames(this ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        var tableName = entity.GetTableName().ToSnakeCase();
        entity.SetTableName(tableName);

        foreach (var property in entity.GetProperties())
        {
            var columnName = property.GetColumnName().ToSnakeCase();
            property.SetColumnName(columnName);
        }

        foreach (var key in entity.GetKeys())
        {
            var keyName = key.GetName().ToSnakeCase();
            key.SetName(keyName);
        }

        foreach (var key in entity.GetForeignKeys())
        {
            var foreignKeyName = key.GetConstraintName().ToSnakeCase();
            key.SetConstraintName(foreignKeyName);
        }

        foreach (var index in entity.GetIndexes())
        {
            var indexName = index.GetName().ToSnakeCase();
            index.SetName(indexName);
        }
    }
}

private static string ToSnakeCase(this string name)
{
    return string.IsNullOrWhiteSpace(name)
        ? name
        : Regex.Replace(
            name,
            @"([a-z0-9])([A-Z])",
            "$1_$2",
            RegexOptions.Compiled,
            TimeSpan.FromSeconds(0.2)).ToLower();
}
Observe que não acessamos mais as propriedades diretamente, pois para alterar os metadados usamos: SetTableName, SetConstraintName, SetName. 😄

Veja como ficou nosso SampleContext

public sealed class SampleContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseNpgsql(
                "Host=127.0.0.1;Username=postgres;Password=XXX;Database=TestSnake", 
                _ => _.EnableRetryOnFailure());
        }
    }

    protected override void OnModelCreating(ModelBuilder modelo)
    {
        modelo.Entity<TestSnakeCase>();

        // Aqui está nossa mágica!
        modelo.ToSnakeNames();
        //..
    }
}

Nossa saída SQL

CREATE TABLE test_snake_case (
    id serial NOT NULL,
    codigo_ibge integer NOT NULL,
    nome_completo text NULL,
    ano_nascimento integer NOT NULL,
    data_cadastro timestamp without time zone NOT NULL,
    CONSTRAINT pk_test_snake_case PRIMARY KEY (id)
);


Veja as alterações na API de metadados

Antes Depois
IEntityType.QueryFilter GetQueryFilter()
IEntityType.DefiningQuery GetDefiningQuery()
IProperty.IsShadowProperty IsShadowProperty()
IProperty.BeforeSaveBehavior GetBeforeSaveBehavior()
IProperty.AfterSaveBehavior GetAfterSaveBehavior()
IEntityType.Relational().TableName IEntityType.GetTableName()
IProperty.Relational().ColumnName IProperty.GetColumnName()
IKey.Relational().Name IKey.GetName()
IForeignKey.Relational().Name IForeignKey.GetConstraintName()
IIndex.Relational().Name IIndex.GetName()

Performance

1 - Em vez de usar Task agora se usa ValueTask o que reduz o número de alocações de memória na pilha.
2 - Você poderia até não saber disso, mas quando acessavamos o método Entry() era disparado um DetectChanges() para todos objetos daquele contexto específico, agora isso não é mais uma verdade :) ualll!

Código Completo!

using Microsoft.EntityFrameworkCore;
using System;
using System.Text.RegularExpressions;

namespace SnakeCase
{
    class Program
    {
        static void Main(string[] args)
        {
            using var db = new SampleContext();
            var script = db.Database.GenerateCreateScript();
            Console.WriteLine(script);
        }
    }

    public sealed class SampleContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseNpgsql(
                    "Host=127.0.0.1;Username=postgres;Password=XXX;Database=TestSnake", 
                    _ => _.EnableRetryOnFailure());
            }
        }

        protected override void OnModelCreating(ModelBuilder modelo)
        {
            modelo.Entity<TestSnakeCase>();

            // Aqui está nossa mágica!
            modelo.ToSnakeNames();
        }
    }

    public static class SnakeCase
    {
        public static void ToSnakeNames(this ModelBuilder modelBuilder)
        {
            foreach (var entity in modelBuilder.Model.GetEntityTypes())
            {
                var tableName = entity.GetTableName().ToSnakeCase();
                entity.SetTableName(tableName);

                foreach (var property in entity.GetProperties())
                {
                    var columnName = property.GetColumnName().ToSnakeCase();
                    property.SetColumnName(columnName);
                }

                foreach (var key in entity.GetKeys())
                {
                    var keyName = key.GetName().ToSnakeCase();
                    key.SetName(keyName);
                }

                foreach (var key in entity.GetForeignKeys())
                {
                    var foreignKeyName = key.GetConstraintName().ToSnakeCase();
                    key.SetConstraintName(foreignKeyName);
                }

                foreach (var index in entity.GetIndexes())
                {
                    var indexName = index.GetName().ToSnakeCase();
                    index.SetName(indexName);
                }
            }
        }

        private static string ToSnakeCase(this string name)
        {
            return string.IsNullOrWhiteSpace(name)
                ? name
                : Regex.Replace(
                    name,
                    @"([a-z0-9])([A-Z])",
                    "$1_$2",
                    RegexOptions.Compiled,
                    TimeSpan.FromSeconds(0.2)).ToLower();
        }
    }

    public class TestSnakeCase
    {
        public int Id { get; set; }
        public int CodigoIBGE { get; set; }
        public string NomeCompleto { get; set; } 
        public int AnoNascimento { get; set; }
        public DateTime DataCadastro { get; set; }
    }
}


Os fontes do exemplo usado está aqui:
https://github.com/ralmsdeveloper/samplesnakecase

Pessoal fico por aqui e um forte abraço! 😄

#mvpbuzz #mvpbr #mvp #developerssergipe #share #vscode #postgresql #efcore31 #netcore31

Deixe um comentário