Professional Documents
Culture Documents
objeto-relacional?
Todos ns, desenvolvedores de software, sabemos da importncia de banco de dados
em nossos sistemas. Necessitamos, a todo instante, realizar consultas aos dados para
atender as solicitaes do usurio, bem como, registrar qualquer informao passada por
este.
Atualmente possumos muitos bancos de dados relacionais, dentre os mais conhecidas
podemos citar: Oracle, MS SQL Server, MySql, etc. Nessas ferramentas, as
informaes so 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 disponveis para venda teria uma tabela
chamada Produtos, composta por campos que caracterizam um produto, tais como:
Cdigo, Nome, Preo, Categoria, etc.
Sem a utilizao de alguma ferramenta, o trabalho do programador seria rduo e muito
vulnervel a falhas. Imagine o mapeamento de uma tabela Usurios criada atravs 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)
)
Depois de abrir a conexo, precisamos preparar um comando para o banco de dados que
enviar umSELECT que devolver a lista de usurios:
SqlCommand comando = new SqlCommand(
"SELECT id, email, password, name, active, date FROM Users",
conexao);
comando.CommandType = System.Data.CommandType.Text;
O comando preparado executado atravs do mtodo ExecuteReader,
esse mtodo
// l o registro
}
Os dados que estamos lendo pertencem ao modelo Usuario, ento vamos guard-los em
uma instncia 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 cdigo para acessar o banco de dados trabalhoso e repetitivo, alm disso, quando
fazemos queries que envolvem valores passados pelo usurio, podemos facilmente
inserir vulnerabilidades que permitem ataques como o SQL Injection (Tcnica utilizada
por hackers para enviar comandos nocivos base de dados, atravs de campos do
formulrio ou URLs, por exemplo).
Alm do cdigo repetitivo e dos problemas de segurana, o que acontece quando
precisamos trocar o banco MySQL, utilizado atualmente, para o Oracle? Nesse caso,
teremos que modificar o cdigo do sistema inteiro, alm das SQLs, para poder suportar
o novo banco.
Esses so apenas alguns dos problemas que temos quando lidamos com o banco de
dados diretamente, mas reparem que para construirmos uma query na tabela de usurios,
precisamos apenas olhar a estrutura da classe Usuario, ou seja, podemos ter uma
ferramenta que dada uma classe, consegue construir as queries necessrias. Ferramentas
Na aba Online Packages, busque por Entity Framework e aps encontrar clique no
boto Install.
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 conexo com o banco de dados. Sempre que
quisermos gravar, recuperar, atualizar ou remover uma entidade faremos isso atravs 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; }
}
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 opo Dataset e depois clique em next.
Quando criamos o banco pelo Visual Studio, as configuraoes de acesso para o banco
criado so colocadas dentro do arquivo de configurao da aplicao, o App.config.
No App.config, as configuraes 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 conexo para o nome do contexto que criamos para a aplicao, 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 configurao padro na string de conexo, toda vez que executarmos
a aplicao, o banco de dados comear vazio! Para fazermos com que o banco no seja
Com isso, o Entity Framework enviar os comandos para criar as tabelas no banco de
dados!
Alm disso, o contexto do Entity Framework pode ser utilizado para gravar usurios no
banco de dados.
Ento vamos criar um novo usurio chamado Victor:
Usuario victor = new Usuario { Nome = "Victor" };
E agora vamos gravar esse usurio no banco de dados. Para isso, utilizaremos a
propriedadeUsuarios que foi declarada no contexto. Essa propriedade representa um
conjunto de entidades que esto gravadas no banco e para inserir um usurio no banco,
precisamos apenas chamar o mtodoAdd nesse conjunto.
var contexto = new EntidadesContext();
Usuario victor = new Usuario { Nome = "Victor" };
contexto.Usuarios.Add(victor);
O programa completo que gera as tabelas e grava a entidade fica da seguinte forma:
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 cdigo simples, conseguimos gravar o usurio no banco de dados utilizando
o Entity Framework.
Explicao
Agora que j entendemos o funcionamento bsico do Entity Framework, vamos
aprender como manipular os dados que temos no banco de dados.
Cada um dos mtodos do DAO precisa do contexto para executar a query no banco de
dados, ento vamos abr-lo no construtor da classe:
public class UsuariosDAO
{
private EntidadesContext contexto;
public UsuariosDAO()
{
this.contexto = new EntidadesContext();
}
}
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 cdigo do Main no precisamos mais nos preocupar com o cdigo 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);
}
Quando executamos a linha acima, o Entity Framework sincroniza o estado dos objetos
com o banco de dados, ou seja, nesse ponto do cdigo o Entity Framework executa um
update no banco de dados para atualizar as informaes do usurio. Ento 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 no so 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 modificaes so enviadas para o banco de dados.
Vimos que para gravarmos uma entidade, precisamos apenas adicion-la ao conjunto do
contexto:
Quando adicionamos uma nova entidade no contexto, ela fica em um estado chamado
Added.
Para removermos uma entidade, precisamos apenas chamar o mtodo Remove na coleo
do contexto passando a instncia que deve ser removida, quando fazemos isso, a
entidade entra em um estado chamado Deleted.
Toda entidade que no est associada ao contexto est em um estado
chamado Detached.
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;
}
Como a chave estrangeira est na tabela produtos, podemos ter vrios produtos
associados a mesma categoria, o que caracteriza um relacionamento muitos para um
(many to one).
Para que o entity framework reconhea 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 aplicao, o entity framework jogar uma InvalidOperationException pois o
estado do banco de dados no est sincronizado com o estado dos modelos, ento
precisamos criar mais uma migration para incluir esses modelos no banco de dados.
Ento 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.
Quando executamos esse cdigo, o Entity Framework tenta gravar o produto no banco
de dados, porm ele percebe que o produto est associado com uma categoria que ainda
no foi gravada e, portanto, tambm grava a categoria no banco de dados. Ao final desse
cdigo, 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;
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 atrves
do mtodoOnModelCreating do EntidadesContext:
public class EntidadesContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// mapeamento do relacionamento aqui!
}
}
Dentro desse mtodo, utilizamos o modelBuilder para definir os relacionamentos
entidade. Para definirmos que estamos mapeando a Categoria, utilizamos o
mtodo Entity:
modelBuilder.Entity<Categoria>()
da
Consistncia de relacionamentos
bidirecionais
Quando executamos esse cdigo, o Entity Framework executa uma query que recupera
apenas a categoria do banco de dados, os produtos relacionados a essa categoria, por
padro, no so carregados.
O Entity Framework carrega os relacionamentos apenas quando necessrio (modo lazy).
Quando pedimos qualquer informao sobre o relacionamento, ele forado 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);
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 comunicao com o banco de dados feita atravs
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. Ento para
listarmos todos os produtos do banco de dados, utilizamos o seguinte cdigo:
var busca = from p in contexto.Produtos select p;
Queremos buscar todos os produtos com preo maior do que 10.0, ento a condio 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 tambm quisermos os produtos com preo maior do que 100.0? Teramos que
escrever uma query diferente. Precisamos definir um parmetro nessa busca que ser o
preo mnimo do produto e como o LINQ se integra com o cdigo C#, podemos
simplesmente utilizar as variveis que esto em escopo dentro da busca:
decimal preco = 100.0;
var busca = from p in contexto.Produtos
where p.Preco > preco
select p;
Com isso, o Entity Framework define uma query com parmetros utilizando a SQL e j
substitui o valor do parmetro!
Repare que na query acima, no precisamos nos preocupar com o join entre as tabelas,
responsabilidade do Entity Framework enviar a SQL correta para o banco de dados.
Podemos tambm utilizar diversas condies na query. Assim como no if do C#,
podmeos utilizar o &&e o || para juntar as condies da query. Por exemplo, se
quisssemos todos os produtos cuja categoria tem um determinado nome e com preo
maior do que um valor mnimo, poderamos 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;
Podemos fazer uma query equivalente utilizando o LINQ. Essa query pode comear
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 nmero de produtos de uma determinada categoria,
podemos utilizar o count da lista:
c.Produtos.Count
Mas a busca do LINQ s pode devolver um objeto, logo precisamos usar uma projeo:
var busca = from c in contexto.Categorias
select new { Categoria = c, NumeroDeProdutos = c.Produtos.Count };
Com isso estamos devolvendo um objeto annimo que contm um campo que guarda a
categoria e outro que guarda o nmero 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 annimo dentro do mtodo que o declara, ento
criaremos uma nova classe para guardar o resultado dessa query:
public class ProdutosPorCategoria
{
public Categoria Categoria { get; set; }
public int NumeroDeProdutos { get; set; }
}
quando necessrio. 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 cdigo 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, porm a
categoria um relacionamento, ento o NHibernate s a carrega quando necessrio
(quando acessamos seu nome), ou seja, para cada produto estamos enviando uma query
para carregar sua categoria. Esse problema conhecido como N + 1 queries.
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 mtodo
include do DbSetinformando qual relacionamento queremos carregar:
var busca = ctx.Produtos.Include("Categoria");
var produtos = busca.List();
Tambm 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();
Com isso, o Entity Framework traz a informao da categoria junto com sua lista de
produtos e com isso, conseguimos evitar o problema de N + 1 queries.
Dentro desse mtodo do DAO, precisamos criar a query do LINQ que far essa busca,
mas como ficar essa query? Comearemos 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 para colocar essa nova condio, precisamos continuar a query que foi iniciada
na varivel 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 cdigo funciona pois as queries do LINQ so enviadas para o banco de dados
apenas quando chamamos o mtodo ToList ou iteramos na busca, alm disso, toda vez
Para diminuir a repetio, podemos utilizar as chamadas de mtodo do LINQ. Toda vez
que queremos incluir uma nova restrio na query, podemos utilizar o mtodo Where.
busca = busca.Where(produto => condio)
Para utilizarmos a sintaxe de mtodos, o tipo da varivel busca deve ser IQueryable,
como DbSetimplementa IQueryable, podemos fazer:
IQueryable<Produto> busca = contexto.Produtos;
Cada venda possui diversos produtos e um produto pode participar de vrias 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
inicializao 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>();
}
}
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 intermediria que guarda os ids
das entidades participantes.
Para mepearmos a lista, precisamos abrir novamente o mtodo OnModelCreating:
public class EntidadesContext : DbContext
{
// mapeamentos
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// outras configuraes
modelBuilder.Entity<Venda>();
}
}
Para configurar que a venda possui uma lista de produtos, utilizamos o mtodo 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();
relacionamento.MapRightKey("Produto_Id");
});
Depois de configurarmos o novo modelo, precisamos criar uma migrao que atualizar
as tabelas do banco de dados. No console do NuGet, criaremos uma nova migrao com
o comando Add-Migration:
Add-Migration CriaVenda
Criando Vendas
Agora que conseguimos mapear os produtos e as vendas, vamos comear 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;
{
public string CPF { get; set; }
}
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 herana em uma nica tabela o padro do Entity
Framework.
Como estamos armazenando dados de vrias entidades em uma nica tabela,
precisamos diferenciar os registros de alguma forma. Para fazer essa
diferenciao, o Entity Framework precisa de uma coluna que discriminar o
tipo de entidade que est gravada naquele registro. Por padro o nome dessa
contexto.Usuarios.Add(caelum);
contexto.SaveChanges();
No mtodo Up, queremos executar uma SQL que vai atualizar as informaes
do banco de dados, fazemos isso com o mtodo Sql da classe DbMigration:
public partial class MigraDadosDoUsuario : DbMigration
{
public override void Up()
{
Sql("sql que eu quero executar no banco");
}
public override void Down() { }
}
modelBuilder.Entity<PessoaJuridica>().ToTable("PessoaJuridica");
}
}
TargetMigration:
Update-Database -TargetMigration:CriaVenda