A ideia de se trabalhar com um ORM é continuar trabalhando no seu código com orientação a objetos sem se preocupar com a persistência dos dados, uma vez que o banco de dados relacional possui outro paradigma. Isso para ser simplista.
Portanto, se queremos usar herança, algo comum no dia-a-dia de quem trabalha com orientação a objetos, é necessário que o ORM escolhido consiga trabalhar com isso.
O Entity Framework é um ORM e ele mapeia heranças. E esse é o assunto deste artigo, para o próximo falar de diferenças de performance diferente entre os três tipos diferentes. Também queria falar dos três tipos, uma vez que meu mestre falou somente de dois ano passado.
Primeiro criando as entidades com Entity Framework Code First
Muito bem, para fazer este texto, eu usei três classes: Jogador, Artilheiro e Goleiro. Artilheiro e Goleiro herdam de Jogador, que é a classe base. Veja a seguir o código da classe Jogador:
public class Jogador { public int Id { get; set; } public string Nome { get; set; } }
A classe Goleiro herda da classe Jogador também e conta gols defendidos e tem um método de fazer peixinho (outra piada ordinária):
public class Artilheiro : Jogador { public int GolsMarcados { get; set; } public void MarcarGol() { } }
A classe de contexto dos três exemplos possui duas coisas em comuns: desabilitam o plurarization e apagam e reconstroem a base sempre.
Vejamos agora a diferença entre os tipos de herança no Entity Framework:
Table Per Hierarchy (TPH)
Também conhecido como Single Table Inheritance, esse é um tipo simples de se usar: você referencia a classe base no seu contexto e usa as classes filhas normalmente no projeto. No fim, o seu banco terá uma única tabela para as classes filhas:
Vejamos na imagem as colunas da tabela Jogador: são todas as da classe pai e as da classe filha, com uma coluna a mais chamada Discriminator. Essa coluna é que vai definir o tipo de tipo do registro – no caso, se ele é Artilheiro ou Goleiro.
Prático.
A seguir, o código da classe de contexto:
public class JogadoresContext:DbContext { public DbSet Jogadores { get; set; } public JogadoresContext() { Database.SetInitializer(new DropCreateDatabaseAlways()); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove(); base.OnModelCreating(modelBuilder); } }
Conclusão: você tem uma forma simples e prática de trabalhar com herança, e todas as classes serão mapeadas como uma única tabela.
Table Per Type (TPT)
Como o nome diz, é o tipo de mapeamento em que temos uma tabela por tipo. Parece mais lógico: uma tabela para cada classe, como seria normalmente se não herdassem uma da outra.
A diferença para se implementá-la é definir quais serão as tabelas. Caso prefira usar Data Annotations, você colocará em cada classe uma annotation com o nome da tabela, como a seguir:
[Table("Jogadores")]
Se preferir pode usar FluenApi, assim você adiciona no modelbuilder as tabelas, como no exemplo a seguir:
modelBuilder.Entity().ToTable("Jogadores"); modelBuilder.Entity().ToTable("Artilheiros"); modelBuilder.Entity().ToTable("Goleiros");
Em qualquer um dos meios, as tabelas ficarão separadas, como na imagem a seguir. Desse jeito, o Id de Artilheiro é o mesmo que o Id de jogador, por exemplo, e os dados do mesmo registro ficam separados em tabelas de cada um de seus tipos.
Conclusão: esse meio é indicado para manter o banco de dados normalizado.
Table Per Concrete Class (TPC)
A ideia desse tipo de mapeamento no Entity Framework é construir tabelas com base somente nas classes concretas. Ou seja, nas classes filhas, mantendo na tabela das filhas os dados da classe mãe.
Ou seja, no nosso exemplo, teremos as classes Artilheiro e Goleiro somente, ambas com o atributo Nome.
A implementação é um pouco menos simples, porém ainda fácil: você irá mapear por Fluent Api indicando herança e trocando o atributo de DBSet de Jogador para um Artilheiro e um Goleiro. A classe completa de contexto é mostrada a seguir:
public class JogadoresContext:DbContext { public DbSet Artilheiros { get; set; } public DbSet Goleiros { get; set; } public JogadoresContext() { Database.SetInitializer(new DropCreateDatabaseAlways()); } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove(); modelBuilder.Entity().Map(m => { m.MapInheritedProperties(); m.ToTable("Artilheiros"); }); modelBuilder.Entity().Map(m => { m.MapInheritedProperties(); m.ToTable("Goleiros"); }); base.OnModelCreating(modelBuilder); } }
Como percebe, também precisaremos mudar nossa persistência, pois trocamos os tipos. O código a seguir mostra a mudança feita:
using (JogadoresContext jc = new JogadoresContext()) { Goleiro goleiro = new Goleiro(); goleiro.Nome = "Rogerio Ceni"; goleiro.GolsDefendidos = 9000; jc.Goleiros.Add(goleiro); Artilheiro artilheiro = new Artilheiro(); artilheiro.Nome = "Pele"; artilheiro.GolsMarcados = 1000; jc.Artilheiros.Add(artilheiro); jc.SaveChanges(); }
O resultado são duas tabelas, como mostrado na figura a seguir.
Bem, esses são os três tipos de jeitos de se trabalhar com Heranças no Entity Framework.
Baixe o exemplo de graça aqui. A fonte do artigo é o blog MayogaX Dev Blog.