EF Core 2.0 - Novos Recursos : Table Splitting e Owned Types


Neste artigo vou apresentar dois novos recursos do Entity Framework Core 2.0 chamados table splitting e owned types.

Em muitos cenários onde estamos usando uma ferramenta ORM como o Entity FrameworkCore ocorre que a classe da entidade pode definir propriedades complexas, como por exemplo, uma classe Cliente que possui uma propriedade Resindencial do tipo Endereço, e a propriedade Endereço esta definida como uma propriedade de navegação na classe Cliente quando o mapeamento da tabela do banco de dados for definido.

Nota: Esse artigo foi baseado no original: http://www.dotnetcurry.com/entityframework/1398/efcore-2-new-features com ajustes e adaptações.

Na versão do EF Core 2.0, temos disponível agora os recursos Table Splitting e Own Type que são usados para gerenciar a geração e o mapeamento da tabela do banco de dados.

Table Splitting

No EF Core 2.0 agora é possível mapear dois ou mais tipos de entidade para a mesma tabela. Sendo que a coluna da chave primária será compartilhada e cada linha irá corresponder a todas as entidades mapeadas.

Na table splitting , o a chave de relacionamento deve ser configurada em todos os tipos de entidades para essas tabelas compartilhadas.

Owned Types

Neste recurso, a entidade proprietária pode compartilhar o mesmo tipo CLR com outro tipo de entidade proprietária.
Para isso deve haver um conjunto de navegação para o outro tipo de entidade pois o tipo de propriedade não pode ser identificado apenas pelo tipo CLR.

Assim, a entidade que contém a definição da navegação é a proprietária e quando o proprietário for consultado os tipos de propriedade serão incluídos por padrão.

Vamos ver como isso funciona na prática..

Recursos usados:

Criando a solução e o projeto no VS Community

Abra o VS 2017 Community e crie uma solução em branco escolhendo : Other Project Types-> Visual Studio Solutions e clicando em Blank Solution;

Informe o nome EFCore2_TableSplitting e clique em OK;

No menu File clique em Add Project e escolha .NET Core  e o template Console App (.NET Core) informando o nome Exemplo_Normal:

Agora vamos incluir as seguintes referências via Nuget em nosso projeto:

No menu Tools clique em Nuget Package Manager e a seguir em Manage Nuget Packages for Solution;

Clique em Browse e localize o pacote e a seguir marque o projeto e clique em Install.

   

Repita o procedimento acima para cada pacote.

Criando o modelo de entidades

Crie uma pasta chamada Models no projeto e a seguir crie as classes : Cliente, Endereco, Regiao e Pais conforme o código mostrado a seguir:

1- Cliente

    public class Cliente
    {
        public int ClienteId { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
        public Endereco Comercial { get; set; }
        public Endereco Residencial { get; set; }
    }

2- Endereco

    public class Endereco
    {
        public string Localizacao { get; set; }
        public string Complemento { get; set; }
        public string CodigoPostal { get; set; }
        public Regiao Regiao { get; set; }
    }

3- Regiao

    public class Regiao
    {
        public int RegiaoId { get; set; }
        public string Cidade { get; set; }
        public string Estado { get; set; }
        public string PaisId { get; set; }
        public Pais Pais { get; set; }
    }

4- Pais

    [Table("Paises")
    public class Pais
    {
        public string PaisId { get; set; }
        public string PaisNome { get; set; }
    }

Resumindo temos o seguinte :

A classe Regiao contém a propriedade PaisId do tipo string e a propriedade Pais do tipo Pais.

A classe Endereco contém a propriedade Regiao do tipo Regiao.

A classe Cliente define as propriedades Residencial e Comercial do tipo Endereco.

Com base neste código teremos a criação de 3 tabelas : Paises, Regioes e Clientes

Para ter certeza de que Cliente possui o tipo Endereco e, desde que o Endereco contém Regiao como uma propriedade, precisamos dividir o mapeamento nas tabelas Pessoa e Regiao.

Vamos definir isso na classe de contexto que iremos criar a seguir.

Na pasta Models crie a classe de contexto chamada ClienteDbContext que herda de DbContext e onde vamos definir o mapeamento:

5- ClienteDbContext

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Exemplo_Normal.Models
{
    public class ClienteDbContext : DbContext
    {
        public DbSet<Cliente> Clientes { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=MACORATTI;Initial Catalog=ClienteDB;Integrated Security=True")
                .UseLoggerFactory(new LoggerFactory().AddConsole());
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //A tabela Cliente vai ter o endereço residencial e sua propriedade
            modelBuilder.Entity<Cliente>()
                 .OwnsOne(p => p.Residencial)
                 .OwnsOne(r => r.Regiao);
            //O Table Splitting, vai gerar duas tabelas: Clientes e Regioes
            modelBuilder.Entity<Cliente>()
                .OwnsOne(p => p.Comercial)
                .ToTable("Regioes").
                OwnsOne(r => r.Regiao);
        }
    }
}

Nesta classe ClienteDbContext definimos a propriedade Clientes do tipo DbSet que vai mapear a entidade Cliente para a tabela Clientes.

No método OnModelCreating temos o código onde o método OwnsOne é usado duas vezes :

  modelBuilder.Entity<Cliente>()
           .OwnsOne(c => c.Residencial)
           .OwnsOne(r => r.Regiao);

Na primeira chamada temos que a entidade Cliente possui a propriedade Residencial do tipo Endereco visto que esta propriedade esta definida na entidade Cliente.

A classe Endereco define a propriedade Regiao do tipo Regiao e a próxima chamada a OwnsOne indica que a propriedade Regiao pertence à classe Endereco.

  modelBuilder.Entity<Cliente>()
                .OwnsOne(p => p.Comercial)
                .ToTable("Regioes").
                OwnsOne(r => r.Regiao);

Neste código acima a propriedade Comercial pertence à classe Cliente e o método ToTable() aceita um string indicando o nome da tabela 'Regioes' que será mapeada com a entidade.

Definindo o código do método Main da classe Program

Vamos agora definir o código no método Main() da classe Program onde vamos criar o banco de dados e as tabelas, incluir alguns dados e realizar o Table Splitting e usar o Owned Type.

Abaixo vemos o código incluido no método Main():

using Exemplo_Normal.Models;
using Microsoft.EntityFrameworkCore;
using System;
using static System.Console;

namespace Exemplo_Normal
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //chama o método para gerenciar o BD
                GerenciaBancoDados();

                //cria uma instância do contexto
                var db = new ClienteDbContext();

                //define um pais
                var brasil = new Pais { PaisId = "BR", PaisNome = "Brasil" };
                db.Add(brasil);

                //cria um cliente com endereco, regiao e pais
                var cliente = new Cliente
                {
                    Nome = "Macoratti",
                    Email = "macoratti@yahoo.com",
                    Residencial = new Endereco()
                    {
                        Localizacao = "Rua Projetada 601",
                        Complemento = "Centro",
                        CodigoPostal = "04506-200",
                        Regiao = new Regiao()
                        {
                            RegiaoId = 1001,
                            Cidade = "Lins",
                            Estado = "São Paulo",
                            Pais = brasil
                        }
                    },
                    Comercial = new Endereco()
                    {
                        Localizacao = "Av. Brasil, 900",
                        Complemento = "Pq. Industrial",
                        CodigoPostal = "04506-100",
                        Regiao = new Regiao()
                        {
                            RegiaoId = 1001,
                            Cidade = "Lins",
                            Estado = "São Paulo",
                            Pais = brasil
                        }
                    }
                };

                //inclui o cliente e persiste no banco de dados
                db.Clientes.Add(cliente);
                db.SaveChanges();

                //obtém a relação de clientes
                var clientes = db.Clientes.ToListAsync().Result;

                //percorre cada cliente e exibe as informações
                foreach (var item in clientes)
                {
                    WriteLine("---------------------------Cliente-------------------------------------");
                    WriteLine($"Cliente {item.Nome} {item.Email}");
                    WriteLine($"Endereço Residencial : {item.Residencial.Localizacao} {item.Residencial.Complemento} {item.Residencial.CodigoPostal}");
                    WriteLine($"Região : {item.Residencial.Regiao.Cidade} {item.Residencial.Regiao.Estado} {item.Residencial.Regiao.Pais}");
                    WriteLine($"Endereço Comercial : {item.Comercial.Localizacao} {item.Comercial.Complemento} {item.Comercial.CodigoPostal}");
                    WriteLine($"Região :  {item.Comercial.Regiao.Cidade} {item.Comercial.Regiao.Estado} {item.Comercial.Regiao.Pais}");
                }
                ReadLine();
            }
            catch (Exception ex)
            {
                WriteLine(ex.Message);
            }
            ReadLine();
        }

        static void GerenciaBancoDados()
        {
            //cria uma instância do contexto e após usar limpa
            using (var ctx = new ClienteDbContext())
            {
                 // dropa(exclui) o banco de dados e as tabelas se eles existirem
                ctx.Database.EnsureDeleted();
                 // cria o banco de dados e as tabelas
                ctx.Database.EnsureCreated();
            }
        }
    }
}

Neste código iniciamos chamando o método GerenciaBancoDados que garante que o banco de dados será criado sempre que executarmos o código.

O restante do código já esta comentado.

Executando o projeto iremos obter o seguinte resultado:

Nota: A figura acima mostra a penas o resultado exibindo as informações de cliente, endereço e região.

Se abrirmos o SQL Server Management Studio iremos ver as tabelas :  Clientes, Paises e Regioes criadas com a seguinte estrutura:

Assim temos:

Aqui vemos os recursos Owned Type e Table Splitting fornecendo um mecanismo para gerenciar a navegação para definir a Entidade Proprietária e mapeando a entidade para mais de uma tabela.

Note que na classe ClienteDbContext definimos apenas uma propriedade DbSet<Cliente>, mas o banco de dados foi criado com 3 tabelas.

Pegue o projeto completo aqui : EFCore2_TableSplitting.zip

Eu sou a videira verdadeira, e meu Pai é o lavrador.
Toda a vara em mim, que não dá fruto, a tira; e limpa toda aquela que dá fruto, para que dê mais fruto.
João 15:1,2

             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti