Quando usado o mapeamento por Data Annotations, as propriedades das entidades POCO (Plain Old CLR Object) são decoradas com atributos, fazendo necessário referência externa, além da “sujeira” dos metadados.
public int ClienteId {get; set;}
[MaxLength(150), Required]
public string Nome { get; set; }
No Fluent Api usamos métodos encadeados fora das entidades, as mesmas permanecem “limpas”, porém precisamos escrever mais código.
HasKey(x => x.ClienteId);
Property(x => x.Nome)
.HasMaxLength(150)
.HasRequired();
Por que usar Fluent Api?
Falando de sistemas construídos usando DDD (Domain Driven Design), logo vem a primeira preocupação:
Como isolar a camada de domínio de tudo, principalmente da persistência de dados?
Como visto anteriormente, usando Data Annotations, somos obrigados a referenciar a dll System.ComponentModel.DataAnnotations na camada de domínio, pois temos que decorar as propriedades com as anotações. A única forma de usufruir do Code First e manter nossas entidades “limpas” sem referências externas, é mapeando o contexto na abordagem Fluent Api. Com esse modelo, as entidades de domínio se tornam “ignorantes” para as configurações de banco de dados, já que esses detalhes ficam isolados na camada de persistência, que por sua vez, sabe quais as entidades que está configurando.
Tudo que é possível configurar com Data Annotations é possível configurar com o Fluent Api, já o inverso não é possível tornando o aprendizado do Fluent Api OBRIGATÓRIO em sistemas mais complexos, o modo com Data Annotations seria mais apropriado em contextos mais simples permitindo maior acoplamento.
Neste artigo vamos construir um pequeno contexto de uma escola e demonstrar as configurações de banco de dados, mapeamento e relacionamentos entre as entidades, que serão espelhadas como relacionamento de tabela no banco de dados.
Bom, abra o Visual Studio 2013, New Project > Other Project Type > Visual Studio Solution, nomeie como EntityFrameworkEscola, após criar a solução, adicione 3 projetos Visual C#.
Nos projetos EntityFrameworkEscola.Console e EntityFrameworkEscola.DataAccess, instale o Entity Framework via Nuget.
PM> Install-Package EntityFramework
Adicione a referência do projeto EntityFrameworkEscola.Domain nos projetos EntityFrameworkEscola.DataAccess e EntityFrameworkEscola.Console e a referência do projeto EntityFrameworkEscola.DataAccess no projeto EntityFrameworkEscola.Console.
Obs: O projeto EntityFrameworkEscola.Console simula uma camada de apresentação e claro, que em um projeto real, o mesmo não iria referenciar a camada de persistência, teríamos que usar algum container de DI, interfaces e etc**,** porém para simplificar este artigo, vamos trabalhar dessa forma.
No projeto EntityFrameworkEscola.Domain, adicione uma pasta chamada Entities e dentro da pasta, crie as seguintes classes conforme as listagens abaixo:
Aluno
namespace EntityFrameworkEscola.Domain.Entities
{
public class Aluno: Pessoa
{
public Aluno()
{
Usuario = new Usuario();
}
public int AlunoId { get; set; }
public virtual Turma Turma { get; set; }
public virtual Usuario Usuario { get; set; }
}
}
Listagem 01 – Aluno.cs
Curso
using System.Collections.Generic;
namespace EntityFrameworkEscola.Domain.Entities
{
public class Curso
{
public Curso()
{
ProfessorLista = new List<Professor>();
Ativo = true;
}
public int CursoId { get; set; }
public string Numero { get; set; }
public string Descricao { get; set; }
public bool Ativo { get; set; }
public virtual ICollection<Professor> ProfessorLista { get; set; }
public override string ToString()
{
return Descricao;
}
}
}
Listagem 02 – Curso.cs
Pessoa
namespace EntityFrameworkEscola.Domain.Entities
{
public abstract class Pessoa
{
public string Nome { get; set; }
}
}
Listagem 03 – Pessoa.cs
Professor
using System.Collections.Generic;
namespace EntityFrameworkEscola.Domain.Entities
{
public class Professor: Pessoa
{
public Professor()
{
CursoLista = new List<Curso>();
TurmaLista = new List<Turma>();
}
public int ProfessorId { get; set; }
public virtual ICollection<Curso> CursoLista { get; set; }
public virtual ICollection<Turma> TurmaLista { get; set; }
public override string ToString()
{
return Nome;
}
}
}
Listagem 04 – Professor.cs
Turma
using System;
using System.Collections.Generic;
namespace EntityFrameworkEscola.Domain.Entities
{
public class Turma
{
public Turma()
{
AlunoLista = new List<Aluno>();
}
public int TurmaId { get; set; }
public DateTime DataInicio { get; set; }
public DateTime DataTermino { get; set; }
public virtual Professor Professor { get; set; }
public virtual Curso Curso { get; set; }
public virtual ICollection<Aluno> AlunoLista { get; set; }
public override string ToString()
{
return String.Format("Turma: {0} - Data Início: {1} - Professor: {2}", Curso.Descricao, DataInicio.ToShortDateString(), Professor.Nome);
}
}
}
Listagem 05 – Turma.cs
Usuario
namespace EntityFrameworkEscola.Domain.Entities
{
public class Usuario
{
public int UsuarioId { get; set; }
public string Email { get; set; }
public string Senha { get; set; }
}
}
Listagem 06 – Usuario.cs
Bom, temos todas as nossas entidades criadas, agora vamos para o projeto EntityFrameworkEscola.DataAccess, crie uma classe com o nome DataContext, nela vamos realizar algumas configurações válidas para o nosso contexto, veja confrome a Listagem 07
using EntityFrameworkEscola.Domain.Entities;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace EntityFrameworkEscola.DataAccess
{
public class DataContext : DbContext
{
public DataContext()
: base("DataContext")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<Aluno> Alunos { get; set; }
public DbSet<Turma> Turmas { get; set; }
public DbSet<Curso> Cursos { get; set; }
public DbSet<Professor> Professores { get; set; }
public DbSet<Usuario> Usuarios { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Properties()
.Where(p => p.Name == p.ReflectedType.Name + "Id")
.Configure(p => p.IsKey());
modelBuilder.Properties<string>()
.Configure(p => p.HasColumnType("varchar"));
modelBuilder.Properties<string>()
.Configure(p => p.HasMaxLength(100));
}
}
}
Listagem 07 – DataContext.cs
Vamos entender o que significa cada uma dessas configurações:
A classe DataContext deve herdar o DbContext do Entity Framework, no construtor da classe base, o nome da connection string deve ser passado por parâmetro, se nada for informado, ele vai procurar uma connection string com o mesmo nome da classe que está configurando os dados, no caso do nosso exemplo, a classe DataContext.
public DataContext()
: base("DataContext")//Passado por parâmetro o nome da connection string
{
...
}
Listagem 08 – Contrutor do DataContext.cs
Connection String que está no projeto EntityFrameworkEscola.Console:
<connectionStrings>
<add name="DataContext"
connectionString="Data Source=(localdb)\v11.0;
Initial Catalog=EFEscola;
Integrated Security=True;
Pooling=False"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Listagem 09 – Connection String.
Dentro do construtor, eu costumo desabilitar o Lazy Loading, esse mecanismo faz com que o Entity Framework carregue automaticamente os relacionamentos em memória, o que pode causar perda de performance ao fazer um select na base de dados, quando essa opção é desabilitada (deixado como false), ele não carrega as dependências automaticamente e quando for necessário carregar, basta usar o método Include():
Configuration.LazyLoadingEnabled = false;
Listagem 10 – Desabilitando o Lazy Loading
Eu também costumo desabilitar a criação de proxy, o Entity Framework por padrão cria um proxy toda vez que é instanciado uma entidade POCO para que possa ser realizado eventuais mudanças e fazer o carregamento automático das propriedades virtuais, como não estamos usando o Lazy Loading habilitado, não tem muito sentido manter a criação de proxy habilitada também.
Configuration.ProxyCreationEnabled = false;
Listagem 11 – Desabilitando a criação de proxy
Na Listagem 12, temos as propriedades DbSet que vão realizar o mapeamento das tabelas do banco de dados nas entidades POCO
public DbSet<Aluno> Alunos { get; set; }
public DbSet<Turma> Turmas { get; set; }
public DbSet<Curso> Cursos { get; set; }
public DbSet<Professor> Professores { get; set; }
public DbSet<Usuario> Usuarios { get; set; }
Listagem 12 – Mapeamento das entidades POCO
Na classe DataContext, como visto anteriormente, fizemos um override no método OnModelCreating, nele vamos realizar algumas configurações que serão válidas para todo o contexto, como definição de nomenclatura das tabelas, definição de chave primária, entre outras.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Aqui vamos remover a pluralização padrão do Etity Framework que é em inglês
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
/*Desabilitamos o delete em cascata em relacionamentos 1:N evitando
ter registros filhos sem registros pai*/
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
//Basicamente a mesma configuração, porém em relacionamenos N:N
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
/*Toda propriedade do tipo string na entidade POCO
seja configurado como VARCHAR no SQL Server*/
modelBuilder.Properties<string>()
.Configure(p => p.HasColumnType("varchar"));
/*Toda propriedade do tipo string na entidade POCO seja configurado como VARCHAR (150) no banco de dados */
modelBuilder.Properties<string>()
.Configure(p => p.HasMaxLength(150));
/*Definimos usando reflexão que toda propriedade que contenha
"Nome da classe" + Id como "CursoId" por exemplo, seja dada como
chave primária, caso não tenha sido especificado*/
modelBuilder.Properties()
.Where(p => p.Name == p.ReflectedType.Name + "Id")
.Configure(p => p.IsKey());
}
Listagem 13 – Método OnModelCreating