EF Core - Implementando IDesignTimeDbContextFactory

 Hoje veremos como implementar a interface IDesignTimeDbContextFactory em um projeto ASP .NET Core quando o arquivo de contexto estiver em um projeto do tipo Class Library separado.

A interface  IDesignTimeDbContextFactory  é uma fábrica para criar instâncias de DbContext derivadas.

Podemos implementar essa interface para habilitar serviços em tempo de design para tipos de contexto que não têm um construtor público padrão. Em tempo de design, instâncias derivadas de DbContext podem ser criadas para permitir experiências  específicas, como migrações.

Os serviços em tempo de design descobrirão automaticamente as implementações dessa interface que estão no assembly de inicialização ou no mesmo assembly que o contexto derivado.

Um cenário muito comum é quando você decide separar as referências do EF Core do projeto Web em um projeto Class Library e neste projeto definir as entidades e o arquivo de contexto.

Ao fazer isso quando você tentar realizar a migração a partir do projeto Web, onde esta o arquivo appsettings.json contendo a string de conexão, usando o comando add-migration Inicial você vai obter a mensagem de erro:

Unable to create an object of type 'AppDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
PM>

ou ainda

Unable to create an object of type ‘AppDbContext’. Add an implementation of ‘IDesignTimeDbContextFactory’ to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.

Nestes casos a solução é criar uma classe concreta que implemente a interface IDesignTimedDbContextFactory no projeto onde esta definido o arquivo appsettings.json e o arquivo Startup.

Vejamos como fazer isso..

Definindo o cenário e a solução

Vamos supor que temos uma solução chamada DemoMvc criada com dois projetos :

  1. DemoMvc.Infrastructure - Contém a definição das entidades e do contexto;
  2. DemoMvc.WebUI - Aplicação ASP .NET Core Mvc;

Assim nossa aplicação ASP .NET Core MVC vai conter o arquivo appsettings.json com a string de conexão com o banco de dados e o projeto Infrastructure que é tipo tipo Class Library que vai conter a classe Product e o arquivo de contexto definido na classe AppDbContext.

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
          : base(options)
    { }
        public DbSet<Product> Products { get; set; }
}

Abaixo vemos a estrutura da solução:

O projeto DemoMvc.WebUI deve conter uma referência ao projeto DemoMvc.Infrastructure e este por sua vez deverá possuir os seguintes pacotes do EF Core instalados:

Agora vamos executar o Migrations para gerar o banco de dados e a tabela.

Precisamos fazer isso a partir do projeto DemoMvc.Infrastructure para gerar a pasta Migrations neste projeto, e para isso, vamos ter que criar a classe AppDbContextFactory no projeto DemoMvc.WebUI, e esta classe deve implementar a interface IDesignTimedDbContextFactory.

Abaixo temos o código da classe AppDbContextFactory com esta implementação:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace DemoMvc.Infrastructure
{
    public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
    {
        public AppDbContext CreateDbContext(string[] args)
        {
            IConfigurationRoot configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();

            // Criando o DbContextOptionsBuilder manualmente
            var builder = new DbContextOptionsBuilder<AppDbContext>();
            // cria a connection string. 
            // requer a connectionstring no appsettings.json
            var connectionString = configuration.GetConnectionString("DefaultConnection");
            builder.UseSqlServer(connectionString);

            // Cria o contexto
            return new AppDbContext(builder.Options);
        }
    }
}

Observe que estamos implementando o método CreateDbContext que retorna um contexto e que estamos definindo o nosso contexto AppDbContext e o nome da string de conexão "DefaultConnection" que será obtida a partir do arquivo appsettings.json.

Agora podemos aplicar o Migrations executando os comandos e definindo como projeto padrão o projeto DemoMvc.Infrastructure.

Após isso, na janela do Package Manager Console podemos emitir o comando: add-migration Inicial

Podemos também emitir o seguinte comando a partir do projeto DemoMvc.WebUI:

Add-Migration Initial -Context AppDbContext -Project DemoMvc.Infrastructure -Verbose

No comando definimos o nome do arquivo de contexto e o projeto onde o arquivo reside.

Após o processamento veremos que o arquivo de script será gerado na pasta Migrations, e, a pasta será gerada no projeto DemoMvc.Infrastructure:

Nota:  Eu tive que remover a declaração <PrivateAssets>all</PrivateAssets> da referência ao pacote Microsoft.EntityFrameworkCore.Design no arquivo de projeto. Assim a referência a este pacote ficou definida assim:

 <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

Pegue o projeto aqui: DemoMvc.zip

Não se turbe o vosso coração; credes em Deus, crede também em mim.
Na casa de meu Pai há muitas moradas. Se assim não fora, eu vo-lo teria dito. Pois vou preparar-vos lugar.

João 14:1,2

Referências:


José Carlos Macoratti