ASP .NET Core - Usando o EF Core em um projeto separado - I

 Neste artigo vou mostrar como usar e configurar o EF Core em um projeto separado na criação de uma aplicação ASP .NET Core MVC.

Ao criar uma aplicação ASP .NET Core MVC usando o template padrão a abordagem mais comumente usada é definir a classe de contexto, as entidades e as referências no mesmo projeto.

Fazendo assim estamos criando um forte acoplamento entre a camada de apresentação, representada pela aplicação MVC e a ferramenta ORM, no caso o EF Core.  Assim, se você resolver mudar o ORM vai afetar também a camada de apresentação.

Uma boa prática e separar a configuração do EF Core em um projeto diferente criando assim o que podemos chamar de uma camada de acesso aos dados ou DAL (Data Access Layer).

Para mostrar isso neste artigo vamos criar uma aplicação ASP .NET Core MVC usando a arquitetura em 4 camadas onde vamos ter os seguintes projetos:

  1. ASP .NET Core MVC Receitas.Mvc - camada de apresentação MVC;
  2. Class Library(.NET Standard) - Receitas.Infra - camada de infraestrutura contendo o contexto, as migrations e a configuração para acesso aos dados via EF Core;
  3. Class Library(.NET Standard) - Receitas.Domain - camada de domínio contendo as entidades e regras de negócio do projeto;
  4. Class Library(.NET Standard) - Receitas.Application - camada que contém as regras da aplicação e os serviços;

Como exemplo vamos criar um projeto que gerencia as informações de receitas.

Como vamos usar as versões do .NET Core 5.0, EF Core 5.0 e ASP .NET Core 5.0 a primeira coisa a fazer é atualizar o seu ambiente para o .NET 5.

Para isso acesse os links abaixo e faça o download e a instalação das seguintes pacotes:

  1. SDK 5.0
  2. Visual Studio 2019 Community (16.8.1 ou superior)
  3. SQL Server Express 2017 (ou superior)

Para gerenciar as informações vamos usar o EF Core 5.0 na abordagem Code-First.

Mãos a obra..

Criando a solução e os projetos

Vamos iniciar criando uma Blank Solution chamada Receitas.

A seguir vamos criar os projetos individuais na solução.

No menu File selecione Add -> New Project, selecione o template Class Library(.NET Standard) e informe o nome Receitas.Domain;

A seguir vamos repetir o procedimento acima e criar os projetos do tipo Class Library(.NET Standard) nomeados como Receitas.Infra e Receitas.Application.

Para criar o projeto Web selecione o menu File-> Add-> New Project e selecione o template ASP .NET Core Web Application e informando o nome Receitas.Mvc

Para tornar mais simples o cenário não vamos habilitar a autenticação do usuário.

A seguir escolha o template Web Application(Model-View-Controller) e clique em Create;

Ao final teremos a solução com  projetos conforme abaixo:

Para concluir vamos definir as referências entre os projetos criados da seguinte forma:

Para incluir uma referência aos projetos Receitas.Domain e Receitas.Application no projeto Infra, clique com o botão direito do mouse sobre o projeto Receitas.Infra e selecione Add-> Project Reference;

Selecione o projeto Receitas.Domain e o projeto Recietas.Application e clique em OK;

Repita o procedimento para o projeto Receitas.Mvc e marque os dois projetos:  Domain e Infra:

Repita o procedimento para o projeto Receitas.Application marcando apenas o projeto Domain.

Para concluir vamos alterar a definição do <TargetFramework> dos projetos Receitas.Application, Receitas.Domain e Receitas.Infra de  .netstandard2.0 para .net5.0 conforme abaixo:

 <PropertyGroup>
      <TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

Criando o modelo de domínio

Vamos iniciar criando as entidades que representam o nosso modelo de domínio, e, no nosso exemplo vamos ter apenas uma entidade chamada Receita.

No projeto Receitas.Domain vamos criar a pasta Entities e nesta pasta vamos criar a classe Receita :

    public class Receita
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Preparo { get; set; }
        public int Tempo { get; set; }
        public string Ingredientes { get; set; }
        public string Imagem { get; set; }
    }

Temos aqui uma classe POCO com 6 propriedades definida como um modelo anêmico que funciona praticamente como um DTO.

Criando a infraestrutura com o EF Core

No projeto Receitas.Infra iniciar vamos iniciar a definição da lógica de acesso aos dados criando uma pasta Context no projeto e nesta pasta vamos criar a classe AppDbContext.

Antes de definir o código da classe vamos incluir neste projeto os seguintes pacotes:

Clique com o botão direito do mouse sobre Dependencies e selecione Manage Nuget Packages;

Selecione a guia Browse e instale cada um dos pacotes mencionados. Ao final teremos o resultado abaixo no projeto:

Agora podemos definir o código da classe AppDbContext que representa o contexto da nossa aplicação onde definimos o mapeamento ORM e as opções para obter o provedor do banco de dados e a string de conexão.

using Microsoft.EntityFrameworkCore;
using Receitas.Domain.Entities;

namespace Receitas.Infra.Context
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        { }

        public DbSet<Receita> Receitas { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Receita>()
                .Property(p => p.Nome)
                .HasMaxLength(80);

            modelBuilder.Entity<Receita>()
                .Property(p => p.Preparo)
                .HasMaxLength(500);

            modelBuilder.Entity<Receita>()
                .Property(p => p.Ingredientes)
                .HasMaxLength(500);

            modelBuilder.Entity<Receita>()
               .Property(p => p.Imagem)
               .HasMaxLength(255);

            modelBuilder.Entity<Receita>()
               .HasData(

                    new Receita
                    {
                        Id = 1,
                        Nome = "Bolo de Chocolate",
                        Preparo = "Em um liquidificador adicione os ovos, o chocolate em pó, a manteiga, a farinha de trigo, o açúcar e o leite, depois bata por 5 minutos.Adicione o fermento e misture com uma espátula delicadamente.Em uma forma untada, " + "despeje a massa e asse em forno médio (180 ºC) preaquecido por cerca de 40 minutos.",
                        Ingredientes = "4 ovos, 4 colheres(sopa) de chocolate em pó, 2 colheres(sopa) de manteiga," +
                        "3 xícaras(chá) de farinha de trigo, 2 xícaras(chá) de açúcar, 2 colheres(sopa) de fermento, 1 xícara(chá) de leite",
                        Imagem = "bolochocolate.jpg",
                        Rendimento ="10 porções",
                        Tempo = 40
                    },
                    new Receita
                    {
                        Id = 2,
                        Nome = "Pudim de Leite Condensado",
                        Preparo = "Primeiro, bata bem os ovos no liquidificador e acrescente o leite condensado e o leite e bata novamente.Asse em forno médio por 45 minutos, com a assadeira redonda dentro de uma maior com água. " +
                        "Espete um garfo para ver se está bem assado.Deixe esfriar e desenforme.",
                        Ingredientes = "1 lata de leite condensado, 1 lata de leite, 3 ovos, 1 chícara de açucar, 1/2 xícara de água",
                        Imagem = "pudimleite.jpg",
                        Rendimento = "8 porções",
                        Tempo = 60
                    }                  
                );
        }
    }
}

Vamos entender o código acima:

1- Definimos as opções do DbContext no construtor usando DbContextOptions onde vamos injetar o contexto no contêiner IoC e definir a string de conexão e o provedor com o banco de dados;

2- Definimos o mapeamento ORM usando a propriedade DbSet<T> mapeando a entidade Receita para tabela Receitas;

3- No método OnModelCreating usamos a Fluent API para definir o tamanho máximo para as propriedades do tipo string;

4- Usamos o método HasData para popular a tabela do banco de dados com dados de duas receitas na aplicação do Migrations;

Agora só precisamos definir a string de conexão com o banco de dados SQL Server no arquivo appsettings.json do projeto Receitas.Mvc :

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=desktop-bhp8771\\sqlexpress;Initial Catalog=ReceitasDB;Integrated Security=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

E a seguir vamos definir a configuração do contexto como um serviço no projeto Receitas.Infra.

Para isso vamos criar a classe DependencyInjection e o método de extensão AddInfrastructure() :

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Receitas.Infra.Context;
namespace Receitas.Infra
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services,
            IConfiguration configuration)
        {
            services.AddDbContext<AppDbContext>(options =>
                options.UseSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    b => b.MigrationsAssembly(typeof(AppDbContext)
                            .Assembly.FullName)));
            services.AddDatabaseDeveloperPageExceptionFilter();
            return services;
        }
    }
}

Agora precisamos definir no projeto Receitas.Mvc no método ConfigureServices do arquivo Startup a utilização deste método de extensão e assim disponibilizar a definição do serviço do contexto para a aplicação MVC.

...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddInfrastructure(Configuration);

            services.AddControllersWithViews();
        }
...

Para concluir precisamos disponibilizar uma instância do arquivo de contexto AppDbContext para o projeto MVC.

Vamos alterar o código do arquivo Program conforme abaixo:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Receitas.Infra.Context;
using System;
namespace Receitas.Mvc	
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<AppDbContext>();
                    context.Database.Migrate();
                }
                catch (Exception ex)
                {
                    var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "Ocorreu um erro na Migração ou alimentação dos dados.");
                }
            }
            host.Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Neste código , quando a aplicação MVC for inicializada iremos obter o instância do arquivo de contexto.

Agora podemos aplicar o Migrations.

No Visual Studio acione o menu Tools-> Nuget Package Manager -> Package Manager Console ;

E na janela do Package Manager Console selecione o projeto Receitas.Infra e defina os comandos do Migrations:

add-migration 'MigracaioInicial' -Context AppDbContext
update-database

Agora vamos emitir o comando update-database para criar o banco de dados e a tabela Receitas:

Abrindo o SQL Server Management Studio podemos confirmar a criação da tabela Receitas contendo dois registros:



Dessa forma nosso projeto MVC não possui referências ao EF Core e todas as referências e a migrações estão no projeto de infraestrutura.

Na próxima parte do artigo vamos continuar criando o controlador e as views para exibir os dados das receitas.

"Bendito o Deus e Pai de nosso Senhor Jesus Cristo, o qual nos abençoou com todas as bênçãos espirituais nos lugares celestiais em Cristo;  Como também nos elegeu nele antes da fundação do mundo, para que fôssemos santos e irrepreensíveis diante dele em amor;"
Efésios 1:3,4

Referências:


José Carlos Macoratti