Motivação
Em projetos onde a arquitetura é centralizada nas classes do domínio, a base de dados geralmente é criada no final da modelagem, por meio de um processo realizado automaticamente por ferramentas específicas ou frameworks.
Seguindo essa linha de projeto, neste artigo veremos como o Entity Framework Core, através do padrão Code First e da Fluent API, permite realizar o mapeamento das classes e a geração automática da base de dados a partir delas. Tudo isso será feito por meio de ferramentas de linha de comando, sem a necessidade de utilizar o Visual Studio.
Passo 1: Modelar as entidades
Para modelar as entidades, que posteriormente serão refletidas em tabelas do banco de dados, criaremos aqui um projeto do tipo Class Library chamado Projeto.Dominio com duas classes: Cliente e Pedido, cujo código pode ser visto na Listagem 1.
01 namespace Projeto.Dominio
02 {
03 public class Cliente
04 {
05 public Nullable<long> Id { get; set; }
06 public string Nome { get; set; }
07 public String Email { get; set; }
08 public IList<Pedido> Pedidos { get; set; }
09 public Cliente()
10 { }
11 }
12 public class Pedido
13 {
14 public Nullable<long> Id { get; set; }
15 public Double Total { get; set; }
16 }
17 }
Note, na linha 8, que o cliente possui uma lista de pedidos, o que representa um relacionamento unidirecional.
Passo 2: Adicionar as dependências
Com o intuito de centralizar as configurações de persistência, criaremos uma segunda Class Library, com o nome Projeto.Repositorio. Feito isso, para que nela o Entity Framework funcione como esperado, precisamos inserir as dependências abaixo no arquivo project.json na seção dependencies:
01 "Microsoft.NETCore.App": "1.0.0",
02 "Microsoft.EntityFrameworkCore": "1.0.0",
03 "Microsoft.EntityFrameworkCore.Design": "1.0.0-preview2-final",
04 "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
05 "Projeto.Dominio": "1.0.0"
O pacote Microsoft.EntityFrameworkCore.Design, na linha 03, é o responsável por permitir a execução do Migrations, que é um recurso do Entity Framework capaz de gerar e alterar bases de dados a partir das classes de modelo do projeto. Já na linha 05, estamos referenciando outra Class Library da nossa aplicação, a Projeto.Dominio. Essa é a forma com que o .NET Core lida com referências entre Class Libraries dentro de um mesmo projeto.
Passo 3: Criar o DbContext
Neste passo criaremos a classe que servirá de intermediária entre o modelo de classes e o banco de dados. Ela será responsável por processar o mapeamento objeto-relacional e fazer com que os dados trafeguem para ambos os lados: domínio e banco.
Sabendo disso, vamos criar um diretório chamado FonteDeDados e dentro dele a classe PersisteContext.cs, cujo código é apresentado na Listagem 2.
01 using Projeto.Dominio;
02 namespace Projeto.Repositorio.FonteDeDados
03 {
04 public class PersisteContext : DbContext
05 {
06 public PersisteContext()
07 { }
08
09 public PersisteContext(DbContextOptions<PersisteContext> opcoes )
10 :base(opcoes)
11 { }
12
13 public DbSet<Cliente> Clientes { get; set; }
14 public DbSet<Pedido> Pedidos { get; set; }
15 }
16 }
Linhas 09 e 10: criamos um novo construtor para permitir a injeção de dependências do ASP.NET Core na camada de apresentação;
Linhas 13 e 14: mapeamos as classes para que sejam reconhecidas no processo de mapeamento objeto-relacional.
Passo 4: Mapear as entidades com Fluent API
A nova versão do EF aposentou a classe EntityTypeConfiguration. Diante disso, não podemos mais criar mapeamentos com a Fluent API separadamente do resto do código do DbContext. A partir de agora utilizaremos a classe ModelBuilder, que é passada como parâmetro ao método sobrescrito OnModelCreating, analisado mais adiante.
Antes de trabalhar diretamente nesse método, no entanto, criaremos na PersisteContext o mapeamento das entidades Cliente e Pedido. Para tal, utilizaremos métodos separados, um para cada entidade, passando ModelBuilder como parâmetro, de acordo com a Listagem 3.
01 private void ConfiguraCliente(ModelBuilder construtorDeModelos)
02 {
03 construtorDeModelos.Entity<Cliente>( etd=>
04 {
05 etd.ToTable("tbCliente");
06 etd.HasKey(c => c.Id).HasName("id");
07 etd.Property(c => c.Id).HasColumnName("id").ValueGeneratedOnAdd();
08 etd.Property(c => c.Nome).HasColumnName("nome").HasMaxLength(100);
09 etd.Property(c => c.Email).HasColumnName("email").HasMaxLength(30);
10 });
11 }
12
13 private void ConfiguraPedido(ModelBuilder construtorDeModelos)
14 {
15 construtorDeModelos.Entity<Pedido>(etd =>
16 {
17 etd.ToTable("tbPedido");
18 etd.HasKey(p => p.Id).HasName("id");
19 etd.Property(p => p.Id).HasColumnName("id").ValueGeneratedOnAdd();
20 etd.Property(p => p.Total).HasColumnName("total");
21 etd.HasOne(c => c.Cliente).WithMany(p => p.Pedidos);
22 });
23 }
Em seguida, sobrescrevemos o método OnModelCreating e fazemos referência aos mapeamentos criados, como vemos na Listagem 4, concluindo assim a nossa classe de contexto.
01 protected override void OnModelCreating(ModelBuilder construtorDeModelos)
02 {
03 construtorDeModelos.ForSqlServerUseIdentityColumns();
04 construtorDeModelos.HasDefaultSchema("ControlePedidos");
05
06 ConfiguraCliente(construtorDeModelos);
07 ConfiguraPedido(construtorDeModelos);
08 }
Linha 3: instruímos o EF para que o banco de dados escolhido converta as chaves primárias utilizando o tipo Identity, que incrementa um valor inteiro a cada novo registro, trabalhando em conjunto com o método ValueGeneratedOnAdd(), visto na Listagem 3;
Linha 04: definimos o nome do banco de dados que será gerado pelo EF;
Linhas 6 e 7: invocamos os métodos criados anteriormente para realizar o mapeamento das duas entidades, evitando que o código do OnModelCreating se torne muito extenso.
Passo 5: Permitir à Class Library executar o Migrations
Até aqui, criamos a nossa camada de repositório desacoplada das demais, mas o Entity Framework Core não permite a criação de bancos de dados via Migrations em Class Libraries.
Caso executemos os comados do Migrations nesse momento, receberemos duas mensagens de erro específicas. A primeira delas é:
"Could not invoke this command on the startup project 'Projeto.Repositorio'.
This preview of Entity Framework tools does not support commands on class library projects in ASP.NET Core and .NET Core applications."
Para resolver essa situação é necessário criar um método Main na mesma Class Library, e para tal precisaremos adicionar uma classe na raiz do Projeto.Repositorio, chamada Principal.cs, cujo código pode ser visto no código abaixo:
01 public class Principal
02 {
03 static void Main(string[] args){}
04 }
Também precisamos abrir o project.json e dentro da seção buildOptions mudar o valor de "emitEntryPoint" para true, indicando que essa Class Library vai precisar gerar um executável. Uma regra básica que podemos utilizar para decidir qual valor atribuir a essa propriedade é: caso haja um método Main, então esse valor deverá ser true, caso não exista esse método, o valor será false.
Ao executarmos novamente qualquer comando do Migrations, receberemos uma outra mensagem de erro:
"No database provider has been configured for this DbContext.
A provider can be configured by overriding the DbContext. OnConfiguring method or by using AddDbContext on the application service provider.
If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext."
Para permitir esse procedimento, de acordo com a mensagem precisamos criar uma classe estendendo uma interface do EF: IDbContextFactory. Na Listagem 5 criamos essa classe dentro da camada de repositório, para que o Migrations a localize e permita a criação do banco de dados.
01 namespace Projeto.Repositorio.FonteDeDados
02 {
03 public class FabricaPersisteContext : IDbContextFactory<PersisteContext>
04 {
05 private const string CONNECTIONSTRING = @"Data Source=GABRIEL-LAPTOP\SQLEXPRESSR2;Initial Catalog=ControlePedidos;Integrated Security=True";
06 public PersisteContext Create(DbContextFactoryOptions options)
07 {
08 var construtor = new DbContextOptionsBuilder<PersisteContext>();
09 construtor.UseSqlServer(CONNECTIONSTRING);
10 return new PersisteContext(construtor.Options);
11 }
12 }
13 }
Assim, quando o DbContext for chamado via injeção de dependências do ASP.NET Core, nas camadas mais altas, automaticamente essa factory será chamada e então devolverá uma nova instância do DbContext, dependendo do tipo de injeção.
Passo 6: Criando a base de dados com Migrations
Para criar a primeira migration, devemos inserir no prompt de comandos o seguinte código:
dotnet ef migrations add Pedidos
Done. To undo this action, use 'dotnet ef migrations remove'
Com esse comando, o Migrations criará uma estrutura semelhante à que vemos na Figura 1, a partir da qual será gerado o nosso banco de dados.
Figura 1. Estrutura criada pelo Migrations
Agora, para sincronizar o modelo de classes com o banco de dados, digitamos o comando abaixo:
dotnet ef database update
Caso tudo ocorra sem quaisquer problemas, receberemos a seguinte resposta do Migrations:
Applying migration '20160710033248_ControlePedidos'.Done.
Nesse momento já podemos verificar se realmente o banco e suas tabelas foram criadas, como mostra a Figura 2.
Figura 2. Banco de dados e tabelas criados
Com essa proposta, quando alterações forem feitas nas entidades, basta adicionar novas migrations e executá-las para que o banco seja atualizado.