Entity Framework -  Definindo um modelo com herança Table Per Hierarchy (TPH)


O ADO .NET Entity Framework foi projetado para permitir que a criação de aplicações com acesso a dados use o modelo de programação feito contra um modelo conceitual ao invés do antigo modelo de programação feito diretamente contra um banco de dados relacional. O objetivo é diminuir a quantidade de código e o tempo de manutenção necessária exigida nas aplicações orientada a dados.

O ADO .NET Entity Framework é um framework que abstrai o esquema de um banco de dados relacional e o apresenta como um modelo conceitual. Abaixo a temos um resumo dos principais componentes do Entity Framework:

Data Source:  Representa a base da arquitetura onde estão armazenados os dados;

Data Providers:  Os dados são acessados por um ADO.NET data provider.  Até o momento tanto o SQL Server como o MySQL são suportados e provavelmente em breve os principais RDBMS do mercado(Oracle, MySQL, DB2, Firebird, Sybase, VistaDB, SQLite, ...)terão um provider para o EF.

Entity Data Model (EDM):  O  Entity Data Model  é constituído de 3 partes:

  • Conceptual schema definition language (CSDL): Declara e define entidades, associações, herança, etc,. As Entity classes são geradas a partir deste esquema;
  • Store schema definition language (SSDL): Metadados que descreve o container de armazenamento (=banco de dados) que persiste os dados;
  • Mapping specification language (MSL): Mapeia as entidades no arquivo CSDL para tabelas descritas no arquivo SSDL;

Entity Client: O EntityClient é um provedor gerenciado ADO.NET que suporta o acesso a dados descritos no EDM. Ele é similar ao SQLClient, e fornece diversos componentes comoEntityCommand, EntityConnection e EntityTransaction;

Object Services: Este componente permite realizar consultas, atualizações, inclusões e exclusões nos dados, expressados como objetos CLR fortemente tipados que são instâncias de entity types. Ele da suporte tanto a consultas Entity SQL como LINQ to Entities;

Entity SQL(ESQL): É uma derivação da Transact-SQL, projetada para consultar e manipular entidades definidas no EDM. Ele dá suporte herança e associação sendo que tanto os componentes Object Services como os componentes Entity Client podem executar instruções Entity SQL;

LINQ to Entities: É uma linguagem de consulta fortemente tipada para consultar entidades definidas no EDM; 

Criar, modificar e deletar objetos e aplicar estas alterações ao banco de dados é muito fácil com o Entity Framework. Através do Object Services que gerencia todas as alterações feitas nos objetos e gera e executa as instruções T-SQL que irão realizar as operações de inclusão, alteração  e exclusão contra a fonte de dados. Tudo isso é feito através da chamada do método SaveChanges do ObjectContext (equivalente ao SubmitChanges do LINQ to SQL).

Quando usamos uma ferramenta OR/M como o Entity Framework desejamos definir da melhor forma possível o nosso modelo de entidades de forma a que tenhamos um bom desempenho e que o esforço necessário para tratar o modelo seja o mínimo possível.

Como todos já sabem o modelo relacional de banco de dados não se ajusta adequadamente ao paradigma da orientação a objetos e uma ferramenta OR/M que se preze deve oferecer recursos para tornar a discrepância entre esses dois mundos o menos traumática possível. 

Na programação orientada a objetos quando um objeto é um tipo de outro objeto podemos usar a herança para compartilhar suas propriedades de forma que as propriedades da classe base são expostas diretamente para o tipo derivado.

A herança é um recurso da orientação objeto que não existe nativamente no mundo relacional e uma ferramenta OR/M deve oferecer recursos para que possamos implementar a herança em nosso modelo de entidades de forma a obter um melhor rendimento. Felizmente o Entity Framework oferece esses recursos.

Nota:  No paradigma da orientação a objetos a herança é muitas vezes usada para representar que apresentam uma relacionamento do tipo É-um. Ou seja quando um objeto é uma especificação do outro objeto.

O Entity Framework suporta 3 modelos diferentes de herança:

  1. Table Per Type (TPT)
  2. Table Per Hierarchy (Single Table Inheritance) (TPH)
  3. Table Per Concrete Class (TPC)

Em um artigo anterior eu tratei do modelo TPT e neste artigo eu vou tratar do modelo TPH.

O modelo Table-per-Hierarchy (TPH) , também conhecido como herança de tabela única, é a forma mais simples e fácil de implementar.

Para implementar esta solução, todos os tipos concretos na hierarquia de herança são armazenados em uma tabela. O Entity Framework precisa conhecer qual o tipo de cada linha e desta forma devemos definir uma coluna discriminadora que identifica o tipo concreto para qual uma linha está mapeada.

Este modelo por ser bem simples não esta normalizado o que faz com que você tenha colunas que contenham valores nulos visto que alguns tipos requerem colunas que outros tipos não necessitam, dessa forma você precisa marcar as colunas que são únicas por tipos de dados como sendo anuláveis (nullabe) mesmo se elas não forem anuláveis, quando você trabalhar com um tipo que exige a coluna.

Esta abordagem não é tão eficiente em termos de espaço em disco consumido, mas a simplicidade e menor quantidade de joins com outras tabelas pode compensar esse problema gerando um melhor desempenho.

Para demonstrar esta solução, vamos considerar o cenário em que temos uma aplicação que trata veículos onde existem muitos tipos de veículos: carros, barcos, aviões, etc.

Para manter o exemplo gerenciáveis, existe uma classe base Veiculo, um objeto Carro que herda de Veiculo, e um objeto Barco que herda de Veiculo. Onde :

Todos esses dados são armazenados em uma única tabela chamada Veiculos, como mostrado na figura abaixo:

A propriedade Id é a chave primária para todos os tipos que esta tabela contém.

A coluna Tipo é a coluna discriminadora, que especifica o objeto que uma linha representa.

As propriedades Veiculo, Fabricante, Modelo e Ano e são comuns a todos os veículos, e elas estão contidos em uma classe base Veículos com o Id.

A coluna TamanhoPneu é obrigatória para todas as instâncias de Carro e é definida na classe de Carro, mas como as instâncias de Barco não tem pneus, esta coluna permite valores nulos.

A propriedade TamanhoPropulsor é obrigatória para todas as instâncias de barco e é definida na classe de Barco, mas os objetos de carro não têm hélices, de modo que a coluna TamanhoPropulsor deve permitir valores nulos.

Vamos então criar um novo projeto no Visual Web Developer 2010 Express Edition do tipo ASP .NET Empty Web Application com o nome TPH_Heranca;

A seguir vamos criar um modelo de entidades usando o Entity Framework. No menu Project clique em Add New Item;

A seguir clique no template Data -> ADO .NET Entity Data Model e informe o nome Veiculos.edmx e clique em Add;

Na próxima janela do assistente escolha a opção : Generate from database e clique em Next>;

A seguir escolha a conexão com um banco de dados onde a tabela Veiculos acima foi criada , no meu exemplo seria o banco de dados Cadastro.mdf, e clique em Next>

A seguir selecione a tabela Veiculos e clique em Finish;

O modelo gerado pode ser visto na figura abaixo.

Clique com o botão direito do mouse sobre o descrito e no menu suspenso selecione Entity...

A seguir informe o nome Carro para entidade e em Base Type selecione Veiculo e clique em OK;

Repita o procedimento acima e informe o nome Barco para a entidade e Veiculo para Base Type;

A final veremos a associação exibida no descritor conforme a figura abaixo:

Clique na entidade Carro e inclua a propriedade TamanhoPneu; e a seguir inclua a propriedade TamanhoPropulsor na entidade Barco;

Defina o mapeamento para a propriedade TamanhoPneu para o tipo Carro;

Defina o mapeamento para a propriedade TamanhoPropulsor para o tipo Barco:

Se ocorrer o erro 3032 para corrigir o erro remove o tipo da entidade Veiculo;

Agora compile o projeto e verifique se não existem mais erros:

Testando o mapeamento

Para testar o mapeamento vamos incluir alguns dados na tabela Veículos conforme a figura abaixo:

A seguir inclua um novo web form no projeto com o nome Default.aspx;

Inclua neste formulário um controle DataGridView e dois controles Buttons conforme o leiaute abaixo:

No evento Click do botão Exibir Carros inclua o código abaixo:

 Protected Sub btnCarros_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCarros.Click

        Dim db = New TPH_heranca.Cadastro_Entities

        Dim consulta = From c In db.Veiculos.OfType(Of TPH_heranca.Carro)()
                               Select c

        gdvDados.DataSource = consulta.ToList()
        gdvDados.DataBind()
    End Sub

Este código seleciona apenas os veículos do tipo Carro;

No evento Click do botão Exibir Barcos inclua o código abaixo:

 Protected Sub btnBarcos_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnBarcos.Click
        Dim db = New TPH_heranca.Cadastro_Entities
        Dim consulta = From b In db.Veiculos.OfType(Of TPH_heranca.Barco)()
                        Select b
        gdvDados.DataSource = consulta.ToList()
        gdvDados.DataBind()
    End Sub

Executando o projeto e clicando no botão  Exibir Barcos veremos a exibição dos veículos do tipo Barco:

Vimos assim, forma bem simples, como implementar a herança do tipo Table Per Class  Hierarchy (TPH)

Pegue o projeto completo aqui: TPH_heranca.zip

Efésios 1:3 "Bendito seja o Deus e Pai de nosso Senhor Jesus Cristo, o qual nos abençoou com todas as bênçãos espirituais nas regiões celestes em Cristo;"

Referências:


José Carlos Macoratti