EntityFramework Core - SnakeCase

13 minuto(s) de leitura - July 28, 2023

01

Fala pessoal, tudo bem?! 💚

Veja como fazer convenções de nomenclatura SnakeCase para o EntityFramework Core!

01

FYI:
Nosso objetivo aqui é mostrar uma solução para o EntityFramework Core + PostgreSQL.
Basicamente existem 4 tipos de nomenclaturas que usamos para escrever nossos códigos: PascalCase, CamelCase, SnakeCase e SpinalCase, já que iremos abordar um assunto que se trata de um dos casos citados, nada mais justo do que resumir cada um deles.

01

ResumidÃO!

PascalCase

Usando Pascal Case em nosso código significa que a primeira letra de cada palavra para o identificador deverá ser maiúscula.
Exemplo:
BlogRafael = "www.ralms.io";

CamelCase

É o mesmo caso do Pascal Case, porém a primeira letra da primeira palavra é minúscula.
Exemplo:
blogRafael = "www.ralms.io";

SnakeCase

Bom esse termo é bem legal e foi estabelecido por Jack Dahlgren, em 2002 quando trabalhava na Intel, esse caso em especial, não tem uma definição especifica quando se trata em deixar as letras do identificador maiúsculo ou minúsculo, basicamente a regra pra ele é colocar um "_" entre as palavras do identificador.
Exemplo:
blog_rafael = "www.ralms.io";
Blog_Rafael = "www.ralms.io";

SpinalCase

Mesma regra do SnakeCase, única diferença é em vez de usar "_", passaremos a utilizar "-" como separador de palavras.
Exemplo:
blog-rafael = "www.ralms.io";
Blog-Rafael = "www.ralms.io";

Vamos codar?!

O que me levou a escrever esse artigo?

Foi a necessidade que eu tive e a improdutividade de ficar digitando aspas em torno dos campos e tabelas em minhas consultas PostgreSQL, isso mesmo, eu sempre escrevi toda estrutura de meu banco com um DDL bem esquematizado.

Mas eu queria usar todo recurso que o EntityFramwork Core me proporciona, o EFCore por Design cria os nomes de tabelas e campos por reflection, isso significa que se tiver uma propriedade PascalCase, da mesma forma será atribuido o nome a este, existe a possibilidade de usarmos propriedades de sombras(ou Fluent API), mas esse é o trabalho que eu não gostaria de fazer e nem me procupar.

Cada um tem sua forma de aplicar nomeclaturas em seus projetos, quando se trata banco de dados eu gosto de utilizar SnakeCase, acredito que é muito mais legível a leitura e suporte.

E foi por isso que escrevi essa pequena extensão para nosso ModelBuilder, tudo isso por que aqueles que vem do SQL Server sabe que ele não se importa com maiúsculas e minúsculas para nomes de colunas e tabelas, cenário muito diferente para PostgreSQL que faz distinção entre maiúsculas e minúsculas, mas aqui está a solução para isso, e então com alguns pequenos ajustes e uma simples função Regex, serei mais feliz 😄, VIVA REGEX!


public static class LinqSnakeCase
{ 
    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(1)).ToLower(); 
    }
}

Veja como ficou nosso SampleContext

using Microsoft.EntityFrameworkCore;
using System;

namespace SnakeCase
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new SampleContext())
            {
                // Nossa saída SQL
                var script = db.Database.GenerateCreateScript();
            }  
        }
    }

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

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


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

#mvpbuzz #mvpbr #mvp #developerssergipe #share #vscode #postgresql #efcore

Deixe um comentário