32
O que é uma ferramenta de mapeamento objeto-relacional? Todos nós, desenvolvedores de software, sabemos da importância de banco de dados em nossos sistemas. Necessitamos, a todo instante, realizar consultas aos dados para atender as solicitações do usuário, bem como, registrar qualquer informação passada por este. Atualmente possuímos muitos bancos de dados relacionais, dentre os mais conhecidas podemos citar: Oracle, MS SQL Server, MySql, etc. Nessas ferramentas, as informações são armazenadas como registros de tabelas. Por exemplo, imagine o sistema para gerenciamento de produtos em uma loja virtual. O banco de dados para armazenar os produtos disponíveis para venda teria uma tabela chamada Produtos, composta por campos que caracterizam um produto, tais como: Código, Nome, Preço, Categoria, etc. Sem a utilização de alguma ferramenta, o trabalho do programador seria árduo e muito vulnerável a falhas. Imagine o mapeamento de uma tabela Usuários criada através do seguinte comando SQL: create table Users ( id int UNSIGNED NOT NULL AUTO_INCREMENT, email varchar (255) NOT NULL, password varchar (20) NOT NULL, name varchar (255) NOT NULL, active bit (1), date datetime NOT NULL, PRIMARY KEY(id) ) Em nosso sistema temos a necessidade de listar todos os usuários cadastrados no banco. Quando queremos acessar o banco de dados, precisamos de uma conexão: SqlConnection conexao = new SqlConnection("String de conexão com o MS SQL"); conexao.Open(); Depois de abrir a conexão, precisamos preparar um comando para o banco de dados que enviará umSELECT que devolverá a lista de usuários: SqlCommand comando = new SqlCommand( "SELECT id, email, password, name, active, date FROM Users", conexao); comando.CommandType = System.Data.CommandType.Text; O comando preparado é executado através do método ExecuteReader, esse método devolve um objeto especializado em ler o resultado da busca: IDataReader reader = comando.ExecuteReader(); Agora que temos o leitor do resultado, precisamos transformá-lo em uma lista de modelos da aplicação. O IDataReader possui o método Read que é responsável por ler os resultados devolvidos pela busca. Cada vez que chamamos o Read, estamos avançando para o próximo registro da busca. Enquanto for possível avançar, o Read devolve o valor true, o false é devolvido quando não há mais registros na busca. O código para ler todos os registros fica da seguinte forma: while (reader.Read()) {

Entity Framework

Embed Size (px)

DESCRIPTION

Entity Framework

Citation preview

Page 1: Entity Framework

O que é uma ferramenta de mapeamento objeto-relacional?

Todos nós, desenvolvedores de software, sabemos da importância de banco de dados

em nossos sistemas. Necessitamos, a todo instante, realizar consultas aos dados para

atender as solicitações do usuário, bem como, registrar qualquer informação passada por

este.

Atualmente possuímos muitos bancos de dados relacionais, dentre os mais conhecidas

podemos citar: Oracle, MS SQL Server, MySql, etc. Nessas ferramentas, as

informações são armazenadas como registros de tabelas.

Por exemplo, imagine o sistema para gerenciamento de produtos em uma loja virtual. O

banco de dados para armazenar os produtos disponíveis para venda teria uma tabela

chamada Produtos, composta por campos que caracterizam um produto, tais como:

Código, Nome, Preço, Categoria, etc.

Sem a utilização de alguma ferramenta, o trabalho do programador seria árduo e muito

vulnerável a falhas. Imagine o mapeamento de uma tabela Usuários criada através do

seguinte comando SQL:

create table Users (

id int UNSIGNED NOT NULL AUTO_INCREMENT,

email varchar (255) NOT NULL,

password varchar (20) NOT NULL,

name varchar (255) NOT NULL,

active bit (1),

date datetime NOT NULL,

PRIMARY KEY(id)

)

Em nosso sistema temos a necessidade de listar todos os usuários cadastrados no banco.

Quando queremos acessar o banco de dados, precisamos de uma conexão:

SqlConnection conexao = new SqlConnection("String de conexão com o MS SQL");

conexao.Open();

Depois de abrir a conexão, precisamos preparar um comando para o banco de dados que

enviará umSELECT que devolverá a lista de usuários: SqlCommand comando = new SqlCommand(

"SELECT id, email, password, name, active, date FROM Users",

conexao);

comando.CommandType = System.Data.CommandType.Text;

O comando preparado é executado através do método ExecuteReader, esse método

devolve um objeto especializado em ler o resultado da busca: IDataReader reader = comando.ExecuteReader();

Agora que temos o leitor do resultado, precisamos transformá-lo em uma lista de

modelos da aplicação. O IDataReader possui o método Read que é responsável por ler os

resultados devolvidos pela busca.

Cada vez que chamamos o Read, estamos avançando para o próximo registro da busca.

Enquanto for possível avançar, o Read devolve o valor true, o false é devolvido

quando não há mais registros na busca. O código para ler todos os registros fica da

seguinte forma: while (reader.Read())

{

Page 2: Entity Framework

// lê o registro

}

Podemos acessar as informações do registro atual utilizando a notação de array, da

mesma forma que fazemos com um dicionário do C#, porém os tipos dos dados

devolvidos pelo reader são tipos do banco de dados e, portanto, precisamos convertê-

los para os tipos do c#. Esse trabalho será feito pela classe Convert: while(reader.Read())

{

int id = Convert.ToInt32(reader["id"]);

}

Os dados que estamos lendo pertencem ao modelo Usuario, então vamos guardá-los em

uma instância dessa classe while(reader.Read())

{

Usuario usuario = new Usuario();

usuario.Id = Convert.ToInt32(reader["id"]);

usuario.Email = Convert.ToString(reader["email"]);

usuario.Senha = Convert.ToString(reader["pasword"]);

usuario.Nome = Convert.ToString(reader["name"]);

usuario.Ativo = Convert.ToBoolean(reader["active"]);

usuario.DataCadastro = Convert.ToDateTime(reader["date"]);

}

O loop lê o registro, cria o usuário com os dados preenchidos e imediatamente perde a

referência para o usuário criado. Para não perdemos a referência, vamos guardá-la em

uma lista:

IList<Usuario> usuarios = new List<Usuario>();

while(reader.Read())

{

Usuario usuario = new Usuario();

usuario.Id = Convert.ToInt32(reader["id"]);

usuario.Email = Convert.ToString(reader["email"]);

usuario.Senha = Convert.ToString(reader["pasword"]);

usuario.Nome = Convert.ToString(reader["name"]);

usuario.Ativo = Convert.ToBoolean(reader["active"]);

usuario.DataCadastro = Convert.ToDateTime(reader["date"]);

usuarios.Add(Usuario);

}

Agora que terminamos de fazer a query, precisamos fechar o reader e a conexão.

reader.Close();

conexao.Close();

O código para acessar o banco de dados é trabalhoso e repetitivo, além disso, quando

fazemos queries que envolvem valores passados pelo usuário, podemos facilmente

inserir vulnerabilidades que permitem ataques como o SQL Injection (Técnica utilizada

por hackers para enviar comandos nocivos à base de dados, através de campos do

formulário ou URLs, por exemplo).

Além do código repetitivo e dos problemas de segurança, o que acontece quando

precisamos trocar o banco MySQL, utilizado atualmente, para o Oracle? Nesse caso,

teremos que modificar o código do sistema inteiro, além das SQLs, para poder suportar

o novo banco.

Esses são apenas alguns dos problemas que temos quando lidamos com o banco de

dados diretamente, mas reparem que para construirmos uma query na tabela de usuários,

precisamos apenas olhar a estrutura da classe Usuario, ou seja, podemos ter uma

ferramenta que dada uma classe, consegue construir as queries necessárias. Ferramentas

Page 3: Entity Framework

que fazem o mapeamento do mundo orientado a objetos (classes e objetos) para o

mundo relacional são chamadas de mapeadores objeto relacional, ou ORM (Object

Relational Mapper). Nesse mapeamento, classes se transformam em tabelas e objetos

em registros das tabelas.

Nesse curso, aprenderemos como funciona o ORM da Microsoft. O Entity Framework!

Instalação do Entity Framework

O Entity Framework é uma ferramenta de mapeamento objeto relacional desenvolvido

pela Microsoft, nele as entidades do banco de dados são mapeadas para coleções de

dados que podem ser utilizadas no LINQ.

Para exemplificarmos o uso do Entity Framework, criaremos uma loja virtual

simplificada, em um projeto chamado LojaEF. Nesse projeto, criaremos classes que

possuem representação no banco de dados, chamaremos essas classes de Entidades.

Vamos criar um projeto do tipo Console Application, que chamaremos de LojaEF

Para utilizarmos o utilizarmos o entity framework no projeto, precisamos instalá-lo e

para isso, utilizaremos o gerenciador de pacotes padrão do Visual Studio, o NuGet.

Dentro do Visual Studio, clique com o botão direito no projeto Loja e escolha a

opção Manage Nuget Packages. Uma janela como a da imagem abaixo será aberta.

Na aba Online Packages, busque por Entity Framework e após encontrar clique no

botão Install.

Page 4: Entity Framework

O próprio Nuget se encarrega de efetuar o download do framework e já referenciar as

dlls necessárias ao seu projeto. Pronto! Você já pode utilizar o Entity Framework em

seu projeto.

Mapeando a primeira entidade com o Entity Framework

Agora que temos o Entity Framework instalado no projeto, podemos escrever a primeira

entidade, a classe Usuario. Ela será uma entidade do domínio da aplicação, que ficará

dentro do namespaceLojaEF.Entidades

O usuário terá um ID, que será o identificador único do usuário e um Nome:

public class Usuario

{

public int ID { get; set; }

public string Nome { get; set; }

}

Quando utilizamos o Entity Framework, não precisamos mais nos preocupar com o

banco de dados. Essa responsabilidade agora é do ORM, nossa preocupação será apenas

com o domínio da aplicação.

Page 5: Entity Framework

Agora que a classe foi criada, precisamos dizer ao Entity Framework que ela representa

uma entidade do banco de dados. Para isso, ela deve ser colocada dentro de uma classe

que herda de DbContext, o contexto do Entity Framework: public class EntidadesContext : DbContext

{

}

Essa classe funcionará como uma conexão com o banco de dados. Sempre que

quisermos gravar, recuperar, atualizar ou remover uma entidade faremos isso através do

contexto. Para mapearmos oUsuario, precisamos apenas definir uma propriedade do

tipo DbSet dentro do EntidadesContext: public class EntidadesContext : DbContext

{

public DbSet<Usuario> Usuarios { get; set; }

}

O Entity Framework segue o padrão Convention Over Configuration no mapeamento

das classes, o nome da tabela que conterá as informações de uma entidade é o plural do

nome da classe. Cada atributo da entidade se transformará em uma coluna com o

mesmo nome do atributo. O ID da entidade será o atributo que contém a palavra ID no

nome.

Agora que terminamos o mapeamento da classe, vamos utilizar o Entity Framework

para gerar as tabelas do banco de dados.

Nessa aplicação utilizaremos o SQLServer de testes que é integrado ao visual studio.

Para criar o banco de dados que será utilizado, dentro do Solution Explorer, clique com

o botão direito no projeto e selecione a opção Add > New Item. No canto esquerdo da

janela do New Item, escolha a opção Datae depois Service Based Database.

Escolha LojaEF.mdf como nome do novo banco de dados e depois clique no botão Add.

Page 6: Entity Framework

Quando clicarmos no Add, o Visual Studio abrirá um assistente para nos ajudar a

configurar o banco de dados, o Data Source Configuration Wizard. Na janela aberta,

escolha a opção Dataset e depois clique em next.

Page 7: Entity Framework

Depois que o assistente terminar de configurar o banco de dados, clique no

botão Finish

Page 8: Entity Framework

Quando criamos o banco pelo Visual Studio, as configuraçãoes de acesso para o banco

criado são colocadas dentro do arquivo de configuração da aplicação, o App.config.

No App.config, as configurações do banco de dados ficam dentro de uma tag chamada

connectionStrings:

<connectionStrings>

<add name="LojaEF.Properties.Settings.LojaEFConnectionString"

connectionString="Data

Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\LojaEF.mdf;Integrated Security=True"

providerName="System.Data.SqlClient" />

</connectionStrings>

Para utilizarmos o Entity Framework com o banco criado, precisamos mudar o nome da

string de conexão para o nome do contexto que criamos para a aplicação, o

EntidadesContext:

<connectionStrings>

<add name="EntidadesContext"

connectionString="Data

Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\LojaEF.mdf;Integrated Security=True"

providerName="System.Data.SqlClient" />

</connectionStrings>

Se deixarmos essa configuração padrão na string de conexão, toda vez que executarmos

a aplicação, o banco de dados começará vazio! Para fazermos com que o banco não seja

Page 9: Entity Framework

apagado, colocaremos o caminho absoluto do banco na

configuração AttachDbFilename da string de conexão.

Quando queremos trabalhar com outros bancos de dados, precisamos apenas modificar

o arquivo de configuração da aplicação!

<connectionStrings>

<add name="EntidadesContext"

connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=Caminho Absolu to

Banco;Integrated Security=True"

providerName="System.Data.SqlClient" />

</connectionStrings>

Se quisermos mudar o banco de dados da aplicação, precisamos apenas modificar as

configurações que estão no App.config, não precisamos modificar o código fonte da

aplicação!

Agora que já configuramos o banco de dados, vamos utilizar o contexto para gerar as

tabelas do banco. Para isso, utilizaremos a propriedade Database do contexto. Dentro do

método Main, vamos criar um novo contexto: class Program

{

static void Main(string [] args)

{

var contexto = new ExtidadesContext();

}

}

E agora que temos o contexto, precisamos apenas chamar a

método CreateIfNotExists da propriedade Database do contexto. var contexto = new EntidadesContext();

contexto.Database.CreateIfNotExists();

Com isso, o Entity Framework enviará os comandos para criar as tabelas no banco de

dados!

Além disso, o contexto do Entity Framework pode ser utilizado para gravar usuários no

banco de dados.

Então vamos criar um novo usuário chamado Victor:

Usuario victor = new Usuario { Nome = "Victor" };

E agora vamos gravar esse usuário no banco de dados. Para isso, utilizaremos a

propriedadeUsuarios que foi declarada no contexto. Essa propriedade representa um

conjunto de entidades que estão gravadas no banco e para inserir um usuário no banco,

precisamos apenas chamar o métodoAdd nesse conjunto. var contexto = new EntidadesContext();

Usuario victor = new Usuario { Nome = "Victor" };

contexto.Usuarios.Add(victor);

E agora que terminamos de adicionar as informações no contexto, precisamos apenas

salvar as modificações:

contexto.SaveChanges();

E por fim, precisamos fechar o contexto utilizando o método Dispose. contexto.Dispose();

O programa completo que gera as tabelas e grava a entidade fica da seguinte forma:

Page 10: Entity Framework

class Program

{

static void Main(string[] args)

{

var contexto = new EntidadesContext();

contexto.Database.CreateIfNotExists();

Usuario victor = new Usuario { Nome = "victor" };

contexto.Usuarios.Add(victor);

contexto.SaveChanges();

contexto.Dispose();

}

}

E com esse código simples, conseguimos gravar o usuário no banco de dados utilizando

o Entity Framework.

Explicação

Agora que já entendemos o funcionamento básico do Entity Framework, vamos

aprender como manipular os dados que temos no banco de dados.

Operações básicas utilizando o Entity Framework

Já vimos que o Entity Framework utiliza o LINQ para manipular os dados gravados no

banco de dados como se fossem coleções do C#, com isso, podemos adicionar um novo

registro no banco fazendo simplesmente um Add na coleção: var contexto = new EntidadesContext();

Usuario usuario = new Usuario()

{

Nome = "victor",

Senha = "123"

};

contexto.Usuarios.Add(usuario);

Agora para efetivarmos as modificações, utilizamos o método SaveChanges no contexto: // envia as modificações para o banco de dados.

contexto.SaveChanges();

Agora que já temos um objeto gravado, queremos recuperá-lo. Para recuperarmos um

usuário pelo ID, utilizamos o método Find do objeto DbSet. var contexto = new EntidadesContext();

// busca o usuário de Id 1 do banco de dados.

Usuario usuario = contexto.Usuarios.Find(1L);

Console.WriteLine(usuario.Nome);

Se quisermos remover um usuário que foi buscado, precisamos apenas removê-lo da

coleção com o método Remove var contexto = new EntidadesContext();

Page 11: Entity Framework

Usuario usuario = contexto.Usuarios.Find(1L);

// remove o usuário da coleção.

contexto.Usuarios.Remove(usuario);

contexto.SaveChanges();

Isolando o acesso ao banco de dados com DAOs

Nos exemplos que utilizamos até o momento, o código de acesso ao banco está no

método Main do programa. Com isso estamos dificultando a manutenção da aplicação.

Precisamos isolar o código que acessa o banco de dados em classes especializadas em

fazer o acesso aos dados, que são conhecidas como Data Access Object ou DAO

Então criaremos um DAO para isolar o código que acessa as informações do usuário no

banco de dados, esse código será isolado na classe UsuariosDAO public class UsuariosDAO

{

public void Adiciona(Usuario usuario)

{

// código para adicionar o usuário

}

public void Remove(Usuario usuario)

{

// código para remover o usuário

}

public Usuario BuscaPorId(long id)

{

// busca o usuário por id

}

}

Cada um dos métodos do DAO precisa do contexto para executar a query no banco de

dados, então vamos abrí-lo no construtor da classe:

public class UsuariosDAO

{

private EntidadesContext contexto;

public UsuariosDAO()

{

this.contexto = new EntidadesContext();

}

}

E agora podemos implementar os métodos dos DAO:

public class UsuariosDAO

{

private EntidadesContext contexto;

public UsuariosDAO()

{

this.contexto = new EntidadesContext();

}

public void Adiciona(Usuario usuario)

{

Page 12: Entity Framework

this.contexto.Usuarios.Add(usuario);

this.contexto.SaveChanges();

}

public void Remove(Usuario usuario)

{

this.contexto.Usuarios.Remove(usuario);

this.contexto.SaveChanges();

}

public Usuario BuscaPorId(long id)

{

return this.contexto.Usuarios.Find(id);

}

}

E agora no código do Main não precisamos mais nos preocupar com o código de acesso

ao banco de dados, podemos simplesmente utilizar o DAO:

static void Main(string[] args)

{

Usuario usuario = new Usuario() { Nome = "victor", Senha = "123" };

UsuariosDAO dao = new UsuariosDAO();

dao.Adiciona(usuario);

}

Estado dos objetos

Agora que já aprendemos como fazer as operações básicas utilizando o entity

framework, vamos aprender como o entity framework gerencia o estado dos objetos do

contexto. Para ilustrar a explicação, vamos utilizar um banco de dados que contém um

usuário de id 1, chamado Victor e com senha 123.

Vimos que para buscar uma entidade do contexto, podemos utilizar o

método Find do DbSet: var contexto = new EntidadesContext();

Usuario usuario = contexto.Usuarios.Find(1);

Agora se quisermos mudar o nome do usuário para Victor Harada, precisamos

simplesmente mudar a propriedade nome do Usuario buscado:

usuario.Nome = "Victor Harada";

Agora que o usuário foi modificado, podemos salvar as modificações no contexto

contexto.SaveChanges();

Quando executamos a linha acima, o Entity Framework sincroniza o estado dos objetos

com o banco de dados, ou seja, nesse ponto do código o Entity Framework executa um

update no banco de dados para atualizar as informações do usuário. Então quando

buscamos um objeto, a entidade devolvida é gerenciada.

Ao buscarmos um objeto, a entidade está em um estado chamado Unchanged, quando

executamos oSaveChanges todas as entidades Unchanged não são modificadas no banco.

Ao mudarmos o valor de uma propriedade o entity framework muda o estado da

entidade para Modified. Ao chamarmos oSaveChanges quando o contexto possui uma

entidade modificada, as modificações são enviadas para o banco de dados.

Vimos que para gravarmos uma entidade, precisamos apenas adicioná-la ao conjunto do

contexto:

Page 13: Entity Framework

var contexto = new EntidadesContext();

Usuario u = new Usuario { Nome = "rodrigo" };

contexto.Usuarios.Add(u);

Quando adicionamos uma nova entidade no contexto, ela fica em um estado chamado

Added.

Para removermos uma entidade, precisamos apenas chamar o método Remove na coleção

do contexto passando a instância que deve ser removida, quando fazemos isso, a

entidade entra em um estado chamado Deleted.

Toda entidade que não está associada ao contexto está em um estado

chamado Detached.

Cadastro de produtos para a loja

Para facilitar a busca de produtos, queremos dividí-los em categorias. Vamos então criar

a entidadesCategoria: public class Categoria

{

public int ID { get; set; }

public string Nome { get; set; }

}

No c#, para representarmos que um produto possui uma categoria, precisamos apenas

criar uma nova propriedade do tipo Categoria na classe Produto: public class Produto

{

// outras propriedades

public Categoria Categoria;

}

Quando criamos um atributo que representa um relacionamento dentro da classe,

precisamos marcar o atributo como virtual:

public class Produto

{

// outras propriedades

public virtual Categoria Categoria;

}

Para representarmos esse relacionamento no banco de dados, precisamos guardar o id da

categoria na tabela de produtos. Uma coluna que guarda o id para outra tabela é

chamada de chave estrangeira e a propriedade que representa o relacionamento dentro

da classe é chamada de Navigation Property. Além de definirmos a Navigation Property

do produto para a categoria, também precisamos definir um atributo que será a chave

estrangeira do produto para a categoria, por convenção, o nome do atributo que

representa a chave estrangeira é ID, ou seja, no caso do produto teríamos CategoriaID: public class Produto

{

// propriedades do produto

// representa a chave estrangeira do produto para

// a categoria

public int CategoriaID { get; set; }

public Categoria Categoria { get; set; }

}

Page 14: Entity Framework

O tipo da chave estrangeira define a obrigatoriedade do relacionamento. Se quisermos

que todo produto tenha obrigatoriamente uma categoria, utilizamos o tipo int. Se a

categoria do produto for opcional, utilizamos o tipo int? como tipo da chave

estrangeira: public class Produto

{

// propriedades do produto

// agora o produto tem opcionalmente uma categoria

public int? CategoriaID { get; set; }

public Categoria Categoria { get; set; }

}

Como a chave estrangeira está na tabela produtos, podemos ter vários produtos

associados a mesma categoria, o que caracteriza um relacionamento muitos para um

(many to one).

Para que o entity framework reconheça essas classes como entidades, precisamos

adicioná-las ao contexto:

public class EntidadesContexto

{

public DbSet<Usuario> Usuarios { get; set; }

public DbSet<Produto> Produtos { get; set; }

public DbSet<Categoria> Categorias { get; set; }

}

E com isso conseguimos mapear o produto com uma categoria, mas quando tentarmos

executar a aplicação, o entity framework jogará uma InvalidOperationException pois o

estado do banco de dados não está sincronizado com o estado dos modelos, então

precisamos criar mais uma migration para incluir esses modelos no banco de dados.

Então vamos abrir novamente o console do NuGet e executar o comando Add-Migration

CriaTabelasProdutoECategoria e depois vamos atualizar o banco com o

comando Update-Database.

Adicionando um produto com categoria

Quando queremos gravar um novo produto no banco de dados, precisamos,

inicialmente, criar e inicializar uma nova instância do objeto Produto

Produto produto = new Produto();

produto.Nome = "Camiseta";

produto.Preco = 10.0;

E depois, para gravá-lo no banco, devemos adicioná-lo ao contexto e gravar as

modificações:

var contexto = new EntidadesContext();

contexto.Produtos.Add(produto);

contexto.SaveChanges();

Temos duas formas de associar um produto com uma categoria. Na primeira,

precisamos apenas colocar uma instância de Categoria no

atributo Categoria do Produto antes de gravá-lo no banco de dados. Produto produto = new Produto();

// inicializa o produto

Page 15: Entity Framework

Categoria categoria = new Categoria();

// inicializa a categoria

produto.Categoria = Categoria;

var contexto = new EntidadesContext();

contexto.Produtos.Add(produto);

contexto.SaveChanges();

Quando executamos esse código, o Entity Framework tenta gravar o produto no banco

de dados, porém ele percebe que o produto está associado com uma categoria que ainda

não foi gravada e, portanto, também grava a categoria no banco de dados. Ao final desse

código, teremos um produto associado com uma nova categoria.

Se quisermos associar o produto com uma categoria existente no banco, precisamos de

uma categoria que esteja associada com o contexto, ou seja, precisamos buscá-la antes

de gravar o produto.

var contexto = new EntidadesContext();

var categoria = contexto.Categorias.Find(1L);

var produto = new Produto();

//inicializa o produto

produto.Categoria = categoria;

contexto.Produtos.Add(categoria);

contexto.SaveChanges();

Com isso, o Entity Framework percebe que o novo produto está associado a uma

categoria já existente e, portanto, adiciona apenas o produto ao banco de dados.

A segunda forma de associarmos o produto com uma categoria é utilizando a

propriedade que representa a chave estrangeira do modelo. Se quisermos, por exemplo,

associar o novo produto com a categoria de id 1, que deve existir no banco de dados,

precisamos apenas colocar o id 1 na propriedade CategoriaID do novo produto: produto.CategoriaID = 1;

Com isso, ao gravarmos o produto no banco de dados, o entity framework

automaticamente associará o novo produto com a categoria de id 1.

Categoria com lista de produtos

Agora que definimos o mapeamento do relacionamento many to one do produto com a

categoria, estamos interessados em pegar todos os produtos associados a uma

determinada categoria.

Quando olhamos o relacionamento do ponto de vista de um produto, temos muitos

produtos associados a uma instância de categoria, porém do ponto de vista da categoria,

temos uma categoria associada a vários produtos, ou seja, temos um relacionamento one

to many.

No mundo relacional, toda vez que temos um relacionamento many to one, existe,

automaticamente, um one to many, ou seja, no mundo relacional, os relacionamentos

são sempre bidirecionais. Na orientação a objetos, não temos relacionamentos

bidirecionais automáticos, toda vez que queremos o relacionamento one to many,

devemos fazer o mapeamento explícito desse relacionamento.

Page 16: Entity Framework

Para mapear a lista de produtos na categoria, colocaremos, inicialmente, a lista como

uma propriedade da categoria:

public class Categoria

{

public int ID { get; set; }

public string Nome { get; set; }

public virtual IList<Produto> Produtos { get; set; }

}

Agora precisamos avisar o entity framework que o essa lista de produtos é a outra ponta

do relacionamento many to one que colocamos dentro do produto. Fazemos isso atráves

do métodoOnModelCreating do EntidadesContext: public class EntidadesContext : DbContext

{

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

// mapeamento do relacionamento aqui!

}

}

Dentro desse método, utilizamos o modelBuilder para definir os relacionamentos da

entidade. Para definirmos que estamos mapeando a Categoria, utilizamos o

método Entity: modelBuilder.Entity<Categoria>()

E agora para definirmos que a categoria tem muitos produtos, utilizamos o

método HasMany passando um lambda que devolve qual é o atributo da categoria que

representa o relacionamento: modelBuilder.Entity<Categoria>()

.HasMany(categoria => categoria.Produtos)

Agora para indicarmos que o relacionamento da propriedade Produtos é a outra ponta

da propriedade Categoria do produto, utilizamos o método WithOptional. Nesse

método precisamos passar um lambda que devolverá a categoria do produto: modelBuilder.Entity<Categoria>()

.HasMany(categoria => categoria.Produtos)

.WithOptional(produto => produto.Categoria);

Com isso, o entity framework sabe as duas pontas do relacionamento definido.

Vamos agora testar tentar listar todas as produtos de uma determinada categoria, para

isso, buscaremos a categoria do banco de dados e depois utilizaremos o foreach para

imprimir todos os produtos:

static void Main(string [] args)

{

var contexto = new EntidadesContext();

Categoria categoria = contexto.Categorias.Find(1L);

foreach(var produto in categoria.Produtos)

{

Console.WriteLine(produto.Nome);

}

contexto.Dispose();

}

Com esse código simples, conseguimos mapear os relacionamentos das entidades.

Consistência de relacionamentos bidirecionais

Page 17: Entity Framework

Imagine que temos a seguinte categoria no banco de dados:

+----+--------------+

| Id | Nome |

+----+--------------+

| 1 | Informática |

+----+--------------+

Associados a essa categoria, temos os seguintes produtos:

+----+---------+--------+-------------+

| Id | Nome | Preco | CategoriaId |

+----+---------+--------+-------------+

| 1 | Teclado | 20.00 | 1 |

| 2 | Monitor | 300.00 | 1 |

+----+---------+--------+-------------+

Podemos recuperar a lista de produtos utilizando o código abaixo:

var context = new EntidadesContext();

Categoria categoria = context.Categorias.Find(1L);

IList<Produto> produtos = categoria.Produtos;

Quando executamos esse código, o Entity Framework executa uma query que recupera

apenas a categoria do banco de dados, os produtos relacionados a essa categoria, por

padrão, não são carregados.

O Entity Framework carrega os relacionamentos apenas quando necessário (modo lazy).

Quando pedimos qualquer informação sobre o relacionamento, ele é forçado a realizar a

busca no banco de dados:

var context = new EntidadesContext();

Categoria categoria = context.Categorias.Find(1L);

IList<Produto> produtos = categoria.Produtos;

Console.WriteLine(produtos.Count);

Esse código imprime 2 no terminal, pois no banco de exemplo temos 2 produtos

associados à categoria de id 1. Agora vamos adicionar mais um produto com a categoria

1 e imprimir novamente o número de produtos dessa categoria:

var context = new EntidadesContext();

Categoria categoria = context.Categorias.Find(1L);

IList<Produto> produtos = categoria.Produtos;

Console.WriteLine(produtos.Count);

Produto produto = new Produto()

{

Categoria = categoria,

Nome = "nome",

Preco = 200.0

};

context.Produtos.Add(produto);

Console.WriteLine(categoria.Produtos.Count);

Nesse código, o primeiro WriteLine continua imprimindo o número 2 no terminal e

depois de adicionarmos o novo produto no contexto, o Console.WriteLine imprime o

número 3, ou seja, o entity framework consegue perceber mudanças no contexto e

sincronizar as pontas do relacionamento!

Page 18: Entity Framework

Agora que já vimos como mapear as entidades e seus relacionamentos, vamos aprender

como escrever consultas no banco de dados com o Entity Framework.

Como vimos anteriormente, a comunicação com o banco de dados é feita através

do DbContext e toda vez que queremos fazer uma query, precisamos utilizar os

conjuntos de entidades que foram mapeados no contexto: EntidadesContext contexto = new EntidadesContext();

Assim como quando estamos trabalhando com listas e conjuntos do C#, fazemos buscas

nos conjuntos do contexto utilizando a Language Integrated Query ou LINQ. Então para

listarmos todos os produtos do banco de dados, utilizamos o seguinte código:

var busca = from p in contexto.Produtos select p;

Agora para transformarmos o resultado da query em uma lista de produtos, precisamos

apenas chamar o método ToList na busca: var busca = from p in contexto.Produtos select p;

IList<Produto> produtos = busca.ToList();

A query definida na variável busca só é enviada para o banco de dados quando

executamos o métodoToList.

E agora, podemos, por exemplo, utilizar o foreach para imprimir os produtos que foram

recuperados do banco de dados:

var busca = from p in contexto.Produtos select p;

IList<Produto> produtos = busca.ToList();

foreach(var produto in produtos)

{

Console.WriteLine(produto.Nome);

}

E se quisermos ordenar o resultado por algum critério, podemos utilizar o orderby do

LINQ: var busca = from p in contexto.Produtos orderby p.Nome select p;

Buscando produtos por preço

Agora que já aprendemos como fazer uma query que lista entidades, vamos aprender

como colocar restrições na busca. Assim como na busca em listas do c#, utilizamos a

instrução where do LINQ: var busca = from p in contexto.Produtos

where condições

select p;

Queremos buscar todos os produtos com preço maior do que 10.0, então a condição da

query deve aceitar apenas produtos com a propriedade Preco maior do que o valor 10.0:

var busca = from p in contexto.Produtos

where p.Preco > 10.0m

select p;

E se também quisermos os produtos com preço maior do que 100.0? Teríamos que

escrever uma query diferente. Precisamos definir um parâmetro nessa busca que será o

preço mínimo do produto e como o LINQ se integra com o código C#, podemos

simplesmente utilizar as variáveis que estão em escopo dentro da busca:

decimal preco = 100.0;

var busca = from p in contexto.Produtos

where p.Preco > preco

select p;

Page 19: Entity Framework

Com isso, o Entity Framework define uma query com parâmetros utilizando a SQL e já

substitui o valor do parâmetro!

Busca de produtos por categoria

Queremos recuperar todos os produtos que pertencem a uma categoria chamada

informática, porém a Categoria é um outro modelo do sistema e é representada por uma

tabela diferente no banco de dados.

Para realizarmos essa busca, utilizando a SQL, teríamos que utilizar o join:

select p.*

from Produto p

inner join Categoria c on p.CategoriaId = c.Id

where c.Nome = 'Informatica'

Com o LINQ, precisamos apenas acessar as propriedades da entidade. Por exemplo,

para filtrarmos todos os produtos de uma categoria cujo nome está em uma variável do

código, utilizaríamos a seguinte query:

string nomeCategoria = "Informatica";

var query = from p in contexto.Produtos

where p.Categoria.Nome == nomeCategoria

select p;

Repare que na query acima, não precisamos nos preocupar com o join entre as tabelas, é

responsabilidade do Entity Framework enviar a SQL correta para o banco de dados.

Podemos também utilizar diversas condições na query. Assim como no if do C#,

podmeos utilizar o &&e o || para juntar as condições da query. Por exemplo, se

quiséssemos todos os produtos cuja categoria tem um determinado nome e com preço

maior do que um valor mínimo, poderíamos utilizar a seguinte query: string nomeCategoria = "Informatica";

decimal precoMinimo = 100.0m;

var query = from p in contexto.Produtos

where p.Categoria.Nome = nomeCategoria and p.Preco > precoMinimo

select p;

Número de produtos por categoria

Agora que já vimos como a HQL funciona, vamos escrever uma query mais avançada.

Queremos recuperar o número de produtos agrupados por categoria. Para resolvermos

esse problema na SQL utilizaríamos o group by. select c.Id, count(p.Id)

from Categoria c inner join Produto p on (c.Id = p.Categoria_Id)

group by c.Id

Podemos fazer uma query equivalente utilizando o LINQ. Essa query pode começar

pela entidade Categoria:

var busca = from c in contexto.Categorias select c

Mas nessa busca precisamos devolver tanto a categoria quanto a quantidade de produtos

associados. Para recuperar o número de produtos de uma determinada categoria,

podemos utilizar o count da lista:

Page 20: Entity Framework

c.Produtos.Count

Colocando essa linha na query, teremos:

var busca = from c in contexto.Categorias select c, c.Produtos.Count

Mas a busca do LINQ só pode devolver um objeto, logo precisamos usar uma projeção:

var busca = from c in contexto.Categorias

select new { Categoria = c, NumeroDeProdutos = c.Produtos.Count };

Com isso estamos devolvendo um objeto anônimo que contém um campo que guarda a

categoria e outro que guarda o número de produtos daquela categoria. E agora podemos

listar os resultados da query e utilizar um foreach para mostrar os resultados:

var busca = from c in contexto.Categorias

select new { Categoria = c, NumeroDeProdutos = c.Produtos.Count };

var resultados = busca.List();

foreach(var resultado in resultados)

{

Console.WriteLine(resultado.Categoria.Nome + " " + resultado.NumeroDeProdutos);

}

Mas só podemos utilizar o tipo anônimo dentro do método que o declara, então

criaremos uma nova classe para guardar o resultado dessa query:

public class ProdutosPorCategoria

{

public Categoria Categoria { get; set; }

public int NumeroDeProdutos { get; set; }

}

Para fazermos a query devolver instâncias de ProdutosPorCategoria ao invés de

instâncias de objetos anônimos, utilizaremos os initializers do c# para construir o

objeto: var busca = from c in contexto.Categorias

select new ProdutosPorCategoria()

{

Categoria = c,

NumeroDeProdutos = c.Produtos.Count

};

E agora, quando listarmos o resultado da query, teremos uma lista

de ProdutosPorCategoria: var busca = from c in contexto.Categorias

select new ProdutosPorCategoria()

{

Categoria = c,

NumeroDeProdutos = c.Produtos.Count

};

IList<ProdutosPorCategoria> resultado = busca.ToList();

Quando desenvolvemos software utilizando o Entity Framework, não precisamos nos

preocupar tanto com o banco de dados relacional, o que facilita muito o

desenvolvimento, mas devemos cuidar para que essas facilidades não acabem

prejudicando a performance do sistema.

Como aprendemos anteriormente, o Entity Framework carrega todos os

relacionamentos de forma lazy, ou seja, os relacionamentos são carregados apenas

Page 21: Entity Framework

quando necessário. Imagine que em nossa loja, queremos imprimir a lista com o nome

de todos os produtos junto com o nome da categoria de cada produto.

var produtos = contexto.Produtos.ToList();

foreach(var produto in produtos)

{

Console.WriteLine(produto.Nome + " - " + produto.Categoria.Nome);

}

No código acima, fazemos uma query para recuperar a lista de todos os produtos e

depois para cada produto imprimimos seu nome e o nome de sua categoria, porém a

categoria é um relacionamento, então o NHibernate só a carrega quando necessário

(quando acessamos seu nome), ou seja, para cada produto estamos enviando uma query

para carregar sua categoria. Esse problema é conhecido como N + 1 queries.

Evitando o problema do N+1

Quando estamos utilizando a SQL, podemos resolver o problema das N + 1 queries

utilizando um join:

select p.*, c.*

from Produto p join Categoria c on p.CategoriaId = c.Id

Podemos fazer algo parecido utilizando o LINQ. Para especificarmos que a query do

LINQ deve recuperar as categorias junto com a lista de produtos, utilizamos o método

include do DbSetinformando qual relacionamento queremos carregar: var busca = ctx.Produtos.Include("Categoria");

var produtos = busca.List();

Também podemos utilizar o Include na busca do LINQ: var busca = from produto in contexto.Produtos.Include("Categoria") select produto;

var produtos = busca.List();

Agora que estamos utilizando o LINQ, a query para trazer a lista de produtos já

carregará os produtos de cada uma das categorias.

N + 1 em relacionamentos to many

Vamos agora pensar numa query que devolve a lista de categorias.

IList<Categoria> categorias = contexto.Categorias.ToList();

Para cada categoria queremos imprimir o tamanho de sua lista de produtos:

IList<Categoria> categorias = contexto.Categorias.ToList();

foreach(var categoria in categorias)

{

Console.WriteLine(categoria.Nome + " - " + categoria.Produtos.Count);

}

Como a lista de produtos é um relacionamento do tipo one to many, ela é carregada de

forma lazy pelo Entity Framework. Quando executamos categoria.Produtos.Count, o

Entity Framework executa uma query que busca os produtos relacionados a categoria.

Temos novamente o problema de N + 1 Queries.

Para resolver o problema de N + 1 queries no relacionamento to many, também

podemos utilizar oInclude: var busca = contexto.Categorias.Include("Produtos");

Page 22: Entity Framework

Com isso, o Entity Framework traz a informação da categoria junto com sua lista de

produtos e com isso, conseguimos evitar o problema de N + 1 queries.

Queremos colocar na aplicação um novo relatório de produtos. Esse relatório filtrará os

produtos por nome, nome da categoria e preço mínimo, porém essas informações são

opcionais.

Quando a o nome é fornecido para a busca, devemos colocar a condição que compara o

nome do produto na busca, senão, devemos ignorar essa condição. Faremos o mesmo

para a categoria e o preço do produto.

Como essa busca acessa o banco de dados para procurar informações sobre produtos,

colocaremos sua implementação dentro da classe ProdutosDAO. public class ProdutosDAO

{

private EntidadesContext contexto;

// implementação dos outros métodos do DAO.

public IList<Produto> BuscaPorNomePrecoMinimoECategoria(string nome,

double precoMinimo, string nomeCategoria)

{

}

}

Dentro desse método do DAO, precisamos criar a query do LINQ que fará essa busca,

mas como ficará essa query? Começaremos com uma query que lista todos os produtos

do banco de dados:

public IList<Produto> BuscaPorNomePrecoMinimoECategoria(string nome,

double precoMinimo, string nomeCategoria)

{

var busca = from produto in contexto.Produtos select produto;

}

Agora se o nome estiver definido, queremos comparar o nome do produto na condição

da query:

var busca = from produto in contexto.Produtos select produto;

if(!String.IsNullOrEmpty(nome))

{

// coloca a nova condição na query

}

Agora para colocar essa nova condição, precisamos continuar a query que foi iniciada

na variável busca. Para isso podemos utilizar a busca dentro do LINQ como se fosse

uma lista!

var busca = from produto in contexto.Produtos select produto;

if(!String.IsNullOrEmpty(nome))

{

busca = from produto in busca where produto.Nome == nome select produto;

}

Esse código funciona pois as queries do LINQ são enviadas para o banco de dados

apenas quando chamamos o método ToList ou iteramos na busca, além disso, toda vez

Page 23: Entity Framework

que utilizamos a variávelbusca em uma nova query, estamos adicionando novas

restrições à busca. Agora precisamos apenas completar os outros ifs da busca: if(preco > 0.0m) {

busca = from produto in busca where produto.Preco > preco select produto;

}

if(!String.IsNullOrEmpty(nomeCategoria))

{

busca = from produto in busca where produto.Categoria.Nome == nomeCategoria;

}

Com isso conseguimos resolver o problema da busca dinâmica. Agora só precisamos

listar os produtos da busca:

return busca.ToList();

O código completo da solução fica da seguinte forma:

public IList<Produto> BuscaPorNomePrecoMinimoECategoria(string nome,

double precoMinimo, string nomeCategoria)

{

var busca = from produto in contexto.Produtos select produto;

if(!String.IsNullOrEmpty(nome))

{

busca = from produto in busca where produto.Nome == nome select produto;

}

if(preco > 0.0m) {

busca = from produto in busca where produto.Preco > preco select produto;

}

if(!String.IsNullOrEmpty(nomeCategoria))

{

busca = from produto in busca where produto.Categoria.Nome == nomeCategoria;

}

return busca.ToList();

}

Busca dinâmica com os métodos do LINQ

Repare que na solução da busca dinâmica estamos a todo instante repetindo o código:

from produto in contexto.Produtos where alguma condição select produto;

Para diminuir a repetição, podemos utilizar as chamadas de método do LINQ. Toda vez

que queremos incluir uma nova restrição na query, podemos utilizar o método Where. busca = busca.Where(produto => condição)

Para utilizarmos a sintaxe de métodos, o tipo da variável busca deve ser IQueryable,

como DbSetimplementa IQueryable, podemos fazer: IQueryable<Produto> busca = contexto.Produtos;

E agora podemos colocar a condição comparando o nome do produto com o seguinte

código:

busca = busca.Where(produto => produto.Nome == nome);

Agora podemos reescrever as condições da busca com o seguinte código:

if(!String.IsNullOrEmpty(nome))

{

busca = busca.Where(produto => produto.Nome == nome);

}

Page 24: Entity Framework

if(preco > 0.0m) {

busca = busca.Where(produto => produto.Preco > preco);

}

if(!String.IsNullOrEmpty(nomeCategoria))

{

busca = busca.Where(produto => produto.Categoria.Nome == nomeCategoria);

}

E o método do DAO fica da seguinte forma:

public IList<Produto> BuscaPorNomePrecoMinimoECategoria(string nome,

double precoMinimo, string nomeCategoria)

{

IQueryable<Produto> busca = contexto.Produtos;

if(!String.IsNullOrEmpty(nome))

{

busca = busca.Where(produto => produto.Nome == nome);

}

if(preco > 0.0m) {

busca = busca.Where(produto => produto.Preco > preco);

}

if(!String.IsNullOrEmpty(nomeCategoria))

{

busca = busca.Where(produto => produto.Categoria.Nome == nomeCategoria);

}

return busca.ToList();

}

Agora que já aprendemos a fazer as operações básicas da loja, vamos implementar as

vendas.

Toda venda é feita para um usuário (relacionamento many to one com o usuário)

public class Venda

{

public virtual int Id { get; set; }

public virtual Usuario Cliente { get; set; }

}

Cada venda possui diversos produtos e um produto pode participar de várias vendas, o

que caracteriza um relacionamento many to many. Para representar o many to many,

colocaremos uma lista de produtos como propriedade da venda e faremos sua

inicialização no construtor da classe:

public class Venda

{

public virtual int Id { get; set; }

public virtual Usuario Cliente { get; set; }

public virtual IList<Produto> Produtos { get; set; }

public Venda()

{

this.Produtos = new List<Produto>();

Page 25: Entity Framework

}

}

Vamos agora mapear a venda dentro do contexto do Entity Framework:

public class EntidadesContext : DbContext

{

// outros mapeamentos

public DbSet<Venda> Vendas;

}

E agora precisamos configurar o contexto para que ele saiba que a lista de vendas é um

relacionamento many-to-many. No banco de dados, para representarmos um

relacionamento many to many, utilizamos uma tabela intermediária que guarda os ids

das entidades participantes.

Para mepearmos a lista, precisamos abrir novamente o método OnModelCreating: public class EntidadesContext : DbContext

{

// mapeamentos

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

// outras configurações

modelBuilder.Entity<Venda>();

}

}

Para configurar que a venda possui uma lista de produtos, utilizamos o método HasMany: modelBuilder.Entity<Venda>()

.HasMany(x => x.Produtos);

Com isso mapeamos que esse relacionamento é to many, para falarmos que ele é many

to many, precisamos utilizar o WithMany: modelBuilder.Entity<Venda>()

.HasMany(x => x.Produtos)

.WithMany();

Para definirmos qual será a tabela de relacionamento, utilizamos o método Map: modelBuilder.Entity<Venda>()

.HasMany(x => x.Produtos)

.WithMany()

.Map(relacionamento => {

// configurações do relacionamento

});

Dentro do método Map, podemos configurar as caracteristicas do relacionamento

Many-to-Many. Para definirmos o nome da tabela de relacionamentos, utilizamos o

método ToTable no relacionamento: relacionamento.ToTable("Venda_Produtos");

Agora para definirmos os nomes das chaves estrangeiras, utilizamos os

métodos MapLeftKey para mapear a chave estrangeira que aponta para

a Venda e MapRightKey para mapearmos a que aponta para o Produto: relacionamento.MapLeftKey("VendaId");

relacionamento.MapRightKey("Produto_Id");

O mapeamento completo do relacionamento fica da seguinte forma:

modelBuilder.Entity<Venda>()

.HasMany(x => x.Produtos)

.WithMany()

.Map(relacionamento => {

relacionamento.ToTable("Venda_Produtos");

relacionamento.MapLeftKey("VendaId");

Page 26: Entity Framework

relacionamento.MapRightKey("Produto_Id");

});

Depois de configurarmos o novo modelo, precisamos criar uma migração que atualizará

as tabelas do banco de dados. No console do NuGet, criaremos uma nova migração com

o comando Add-Migration: Add-Migration CriaVenda

Essa migração criará a tabela de vendas e a tabela que guardará as informações do

relacionamento many-to-many.

Agora precisamos apenas executar a migração com o comando Update-Database no

console do NuGet.

Criando Vendas

Agora que conseguimos mapear os produtos e as vendas, vamos começar a vender

produtos para os clientes!

Quando vendemos um produto, precisamos inicialmente saber para qual cliente estamos

vendendo:

var contexto = new EntidadesContext();

Venda venda = new Venda();

Usuario cliente = contexto.Usuarios.Find(1L);

venda.Cliente = cliente;

Depois de definirmos para quem estamos vendendo, informaremos que o cliente

comprará os produtos com ids 1 e 2, por exemplo.

Produto p1 = contexto.Produtos.Find(1L);

Produto p2 = contexto.Produtos.Find(2L);

Para relacionar os produtos p1 e p2 com a venda, precisamos apenas adicioná-los na

lista de produtos, o Entity Framework cuidará da sincronização com o banco de dados! venda.Produtos.Add(p1);

venda.Produtos.Add(p2);

Agora que já criamos a venda, vamos adicioná-la ao contexto e salvar as modificações:

contexto.Vendas.Add(venda);

contexto.SaveChanges();

O código completo para a criação da venda fica da seguinte forma:

var contexto = new EntidadesContext();

Venda venda = new Venda();

Usuario cliente = contexto.Usuarios.Find(1L);

venda.Cliente = cliente;

Produto p1 = contexto.Produtos.Find(1L);

Produto p2 = contexto.Produtos.Find(2L);

venda.Produtos.Add(p1);

venda.Produtos.Add(p2);

contexto.Vendas.Add(venda);

contexto.SaveChanges();

Venda para empresas

Page 27: Entity Framework

Nossa loja cresceu muito e agora atende também a revendedores, ou seja, nós

agora vendemos produtos no atacado e no varejo atendendo empresas e

cidadãos comuns, mas nosso modelo atual não atende essa nova regra de

trabalho. Precisamos fazer uma modificação para conseguir identificar se o

cliente é pessoa física ou pessoa jurídica.

Pessoas físicas possuem todos os atributos que definimos para a

entidade Usuario e um atributo chamado CPF.

public class PessoaFisica

{

public int Id { get; set; }

public string Nome { get; set; }

public string Senha { get; set; }

public string CPF { get; set; }

}

Já as pessoas jurídicas possuem, além dos atributos do Usuario, um

atributo CNPJ.

public class PessoaJuridica

{

public int Id { get; set; }

public string Nome { get; set; }

public string Senha { get; set; }

public string CNPJ { get; set; }

}

Esse modelo atende nossa loja, porém teremos de replicar todas as

funcionalidades do Usuario para essas duas novas entidades, o que dificulta a

manutenção do código. Se precisarmos alterar a lógica do cliente,

precisaremos alterar para os dois tipos de cliente. Além disso, para listarmos

todos os clientes da loja, precisaríamos de dois selects, um na

entidade PessoaFisica e outro naPessoaJuridica.

Na orientação a objetos, quando queremos resolver esse tipo de problema,

utilizamos o Polimorfismo. Para aproveitarmos a implementação existente na

classe Usuario, faremos com que as

entidadesPessoaJuridica e PessoaFisica herdem da entidade Usuario.

Os modelos ficarão da seguinte forma:

public class PessoaFisica : Usuario

Page 28: Entity Framework

{

public string CPF { get; set; }

}

public class PessoaJuridica : Usuario

{

public string CNPJ { get; set; }

}

Como todos os clientes do sistema devem ser uma instância ou

de PessoaFisica ou dePessoaJuridica, não queremos permitir que a

classe Usuario seja instanciada, ela, portanto, será uma classe abstrata:

public abstract class Usuario

{

public virtual int Id { get; set; }

public virtual string Nome { get; set; }

}

Agora que já definimos as entidades, precisamos mapeá-las no Entity

Framework. Existem duas estratégias principais para o mapeamento de

Herança:

Tabela Única: O Entity Framework utilizará uma única tabela que conterá todas as propriedades

mapeadas pela classe pai ou por suas filhas.

Uma Tabela por subclasse: Nesse mapeamento, o Entity Framework cria uma tabela que

armazena os dados da classe pai e uma para para cada uma de suas filhas. As tabelas que

representam as classes filhas tem um relacionamente do tipo one to one com a tabela da classe

pai.

Tabela Única

O mapeamento da herança em uma única tabela é o padrão do Entity

Framework.

Como estamos armazenando dados de várias entidades em uma única tabela,

precisamos diferenciar os registros de alguma forma. Para fazer essa

diferenciação, o Entity Framework precisa de uma coluna que discriminará o

tipo de entidade que está gravada naquele registro. Por padrão o nome dessa

Page 29: Entity Framework

coluna especial é Discriminator e o valor que será armazenado será o nome do

tipo guardado.

Como utilizaremos as configurações padrão, não precisamos customizar mais

nada no mapeamento, o Entity Framework fará todo o trabalho sozinho.

Agora para conseguirmos utilizar a herança que acabamos de definir,

precisamos apenas criar uma nova migração dentro do código.

Então no console do nugget vamos adicionar uma nova migração utilizando o

Add-Migration:

Add-Migration HerancaEmTabelaUnica

Quando executarmos a migration, teremos o seguinte banco:

Por fim, vamos inserir dois usuários em nosso banco de dados, um do

tipo PessoaFisica e outro do tipo PessoaJuridica.

EntidadesContext contexto = new EntidadesContext();

PessoaFisica murilo = new PessoaFisica();

murilo.Nome = "Murilo";

murilo.Senha = "987";

murilo.CPF = "123.456.789.00";

contexto.Usuarios.Add(murilo);

PessoaJuridica caelum = new PessoaJuridica();

caelum.Nome = "Caelum";

caelum.Senha = "987";

caelum.CNPJ = "123.456/0001-09";

Page 30: Entity Framework

contexto.Usuarios.Add(caelum);

contexto.SaveChanges();

Com esse código, o Entity Framework gravará as informações tanto da pessoa

física quanto da pessoa jurídica dentro da tabela de usuários e preencherá o

campo discriminador com o nome da classe.

Mas o que acontece com os dados que já estavam gravados no banco de

dados? Como os registros antigos não possuem um valor no campo

discriminador, o Entity Framework não considera essas linhas como resultados

válidos da query e, portanto, elas não são listadas pela coleção de usuários do

contexto.

Quando trabalhamos com banco de dados, muitas vezes precisamos migrar os

dados antigos do banco para que eles sejam compatíveis com a aplicação. No

Entity Framework, podemos resolver esse problema através das migrations!

Vamos adicionar uma nova migração para atualizar a coluna Discriminator do

Usuário.

Add-Migration MigraDadosDoUsuario

Quando executarmos esse comando no console do NuGet, o Entity Framework

criará uma classe de migração que terá os métodos Up e Down com

implementações vazias.

public partial class MigraDadosDoUsuario : DbMigration

{

public override void Up()

{

}

public override void Down()

{

}

}

No método Up, queremos executar uma SQL que vai atualizar as informações

do banco de dados, fazemos isso com o método Sql da classe DbMigration:

public partial class MigraDadosDoUsuario : DbMigration

Page 31: Entity Framework

{

public override void Up()

{

Sql("sql que eu quero executar no banco");

}

public override void Down() { }

}

A Sql que queremos executar deve colocar o valor 'PessoaFisica'

em Discriminator se a coluna estiver vazia.

update tbl_Usuarios set Discriminator='PessoaFisica' where Discriminator=''

Onde tbl_Usuarios é o nome da tabela que guarda as informações dos

usuários. Agora só precisamos colocar essa Sql dentro do método Sql da

migration:

public partial class MigraDadosDoUsuario : DbMigration

{

public override void Up()

{

Sql("update tbl_Usuarios set Discriminator='PessoaFisica' where Discriminator=''");

}

public override void Down() { }

}

E depois de executarmos o comando Update-Database, nossos dados estarão

atualizados.

Uma tabela por subclasse

Quando queremos utilizar uma tabela por subclasse, precisamos mapear

explicitamente as entidades que pertencem à hierarquia de classes

explicitamente dentro do método OnModelCreating doEntidadesContext:

public class EntidadesContext : DbContext

{

public DbSet<Usuario> Usuarios { get; set; }

// outras propriedades

public override void OnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.Entity<PessoaFisica>().ToTable("PessoaFisica");

Page 32: Entity Framework

modelBuilder.Entity<PessoaJuridica>().ToTable("PessoaJuridica");

}

}

Agora para testarmos esse mapeamento, precisamos novamente fazer a

migração do banco de dados, porém nosso banco já está com o primeiro

mapeamento de herança aplicado. Então antes de gerarmos a migração para o

novo mapeamento, precisamos desfazer a migração anterior, voltando para a

última migração antes do mapeamento da herança, que é a migração do Many-

To-Many. Para isso, utilizaremos novamente o comando Update-

Database informando para qual migração queremos ir através da opção -

TargetMigration:

Update-Database -TargetMigration:CriaVenda

Com isso, o Entity Framework executará o método Down das

migrações MigraDadosDoUsuario eHerancaEmTabelaUnica.

Agora vamos apagar as migrações MigraDadosDoUsuario e

HerancaEmTabelaUnica e criar a migração para o novo mapeamento da

Herança:

Add-Migration HerancaTabelaPorClasse

Update-Database

Quando inserirmos novamente a pessoa física, o Entity Framework executará

um insert na tabelaUsuario e um na tabela PessoaJuridica. O mesmo acontecerá

quando inserirmos a pessoa jurídica.