Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As ferramentas de tempo de design são a parte do EF que inicializa as operações de tempo de design, como scaffolding de um modelo ou o gerenciamento de migrações. Eles são responsáveis por instanciar objetos DbContext para uso no tempo de design.
Existem dois pontos de entrada principais: dotnet-ef e as ferramentas do PMC (Console do Gerenciador de Pacotes) NuGet do EF Core. Ambos são responsáveis por coletar informações sobre os projetos do usuário, compilá-los e, em seguida, chamar o ef.exe, que, por fim, chama os pontos de entrada do tempo de design dentro do EFCore.Design.dll.
dotnet-ef
dotnet-ef é uma Ferramenta do .NET. O prefixo dotnet permite que ele seja invocado como parte do comando dotnet principal: dotnet ef
.
Há duas entradas primárias para esse comando: o projeto de inicialização e o projeto de destino. o dotnet-ef é responsável por ler informações sobre esses projetos e compilá-los.
Ele lê informações sobre os projetos, injetando um arquivo .targets do MSBuild e chamando o alvo personalizado do MSBuild. O arquivo .targets é compilado em dotnet-ef como um recurso inserido. A origem está localizada em src/dotnet-ef/Resources/EntityFrameworkCore.targets.
Ele tem um pouco de logica no início para tratar de projetos multiplataforma. Essencialmente, ele escolhe apenas a primeira estrutura de destino e se invoca novamente. Depois que uma única estrutura de destino tiver sido determinada, ela obterá várias propriedades do MSBuild, como AssemblyName, OutputPath, RootNamespace etc.
Depois de coletarmos as informações do projeto, compilamos o projeto de inicialização. Presumimos que o projeto de destino também será compilado transitivamente.
Em seguida, dotnet-ef invoca ef.exe.
Ferramentas do PMC
As ferramentas do PMC executam uma função semelhante à dotnet-ef, mas usam APIs do Visual Studio em vez do MSBuild. Eles são distribuídos como o pacote Microsoft.EntityFrameworkCore.Tools. Eles são um módulo especial do PowerShell com reconhecimento do VS que é carregado automaticamente no Console do Gerenciador de Pacotes NuGet por meio do init.ps1. Assim como o dotnet-ef, cada comando usa duas entradas primárias: o projeto de inicialização e o projeto de destino. Mas os valores padrão destes são obtidos do IDE. O projeto de destino usa como padrão o projeto especificado como o projeto padrão dentro do Console do Gerenciador de Pacotes. O projeto de inicialização usa como padrão aquele especificado como o projeto de inicialização (via Set as Startup Project) no Gerenciador de Soluções.
As ferramentas PMC coletam informações sobre os projetos por meio das APIs EnvDTE sempre que possível. Ocasionalmente, é necessário recorrer às APIs do Common Project System (CPS) ou do MSBuild. A fonte de implementação moderna do sistema de projetos do C# está disponível no projeto dotnet/project-system no GitHub.
Depois de coletar as informações, ela cria toda a solução.
Dica
O problema nº 9716 é a atualização para criar apenas o projeto de inicialização.
Em seguida, como dotnet-ef, ele invoca ef.exe. As Ferramentas do PMC têm um pouco de lógica extra depois de invocar ef.exe para abrir todos os arquivos criados por um comando para fornecer uma experiência mais integrada.
ef.exe
Às vezes referenciado como o elemento interno, o ef.exe (por falta de um nome melhor) é remetido como parte do dotnet-ef e das Ferramentas do PMC como um conjunto de binários. Há vários binários para diferentes estruturas e plataformas de destino.
- Ferramentas/
- net461/
- any/
- ef.exe
- win-x86/
- ef.exe
- win-arm64/
- ef.exe
- any/
- netcoreapp2.0/
- any/
- ef.dll
- any/
- net461/
Os assemblies do .NET Framework são invocados apenas para projetos do EF Core 3.1 e anteriores que têm como destino o .NET Framework. Por design, você pode usar a versão mais recente das ferramentas em projetos que usam versões mais antigas do EF. Não existe x64 porque o assembly em qualquer diretório direciona a plataforma AnyCPU, que é executada como x64 nas versões x64 e arm64 do Windows.
O assembly .NET Core 2.0 é usado para projetos com destino ao .NET Core ou ao .NET 5 e mais recente.
A principal responsabilidade do ef.exe é carregar o assembly de saída do projeto de inicialização e invocar os pontos de entrada do tempo de design dentro da EFCore.Design.dll.
No .NET Framework, usamos um AppDomain separado para carregar o assembly do projeto, passando o arquivo App/Web.config do projeto para honrar e associar redirecionamentos adicionados pelo NuGet ou pelo usuário.
No .NET Core/5+, invocamos ef.dll usando os arquivos .deps.json e .runtimeconfig.json do projeto para emular o comportamento real de carregamento de runtime e assembly do projeto.
dotnet exec ef.dll --depsfile startupProject.deps.json --runtimeconfig startupProject.runtimeconfig.json
Dica
O problema nº 18840 é principalmente sobre o uso do AssemblyLoadContext em vez de dotnet exec
para carregar o assembly do usuário. Isso deve permitir que as ferramentas funcionem com mais tipos de projeto, incluindo aqueles direcionados ao Android e ao iOS.
Depois que tudo estiver configurado para ser carregado, ef.exe faz uma chamada para EFCore.Design.dll usando reflexão e Activator.CreateInstance (ou AppDomain.CreateInstance no .NET Framework).
EFCore.Design.dll
EFCore.Design.dll, ou, mais precisamente, Microsoft.EntityFrameworkCore.Design.dll, contém toda a lógica de design-time do EF Core. Todos os pontos de entrada estão dentro da classe OperationExecutor. Grande parte da estranheza no design dessa classe (MarshallByRefObject, tipos aninhados etc.) decorre da necessidade de invocá-la entre AppDomains no .NET Framework. Muito poderia ser simplificado se esse requisito fosse removido. Todas as assinaturas são fracamente tipadas para possibilitar a compatibilidade das ferramentas com versões posteriores e anteriores. Lembre-se de que diferentes versões das ferramentas podem ser usadas para invocar projetos usando diferentes versões do EF.
Além do executor, DbContextActivator é outro tipo importante nesta montagem. É usado por alguns dos componentes do ASP.NET Web Tools para criar uma instância do DbContext do usuário no tempo de design.
Criando um DbContext
Antes que qualquer lógica de tempo de design específica seja executada, uma instância DbContext normalmente é necessária. O usuário pode especificar um nome de tipo simples ou totalmente qualificado para o DbContext, sem diferença entre maiúsculas e minúsculas, ou optar por não especificar um caso haja apenas um único tipo de DbContext. De qualquer forma, precisamos descobrir todos os tipos DbContext antes de reduzi-lo a um único. A lógica para descobrir tipos DbContext reside no método FindContextTypes de DbContextOperations.
Procuramos os tipos de DbContext usando várias fontes.
- Referenciado pelas implementações de IDesignTimeDbContextFactory<T> no assembly de inicialização.
- DbContexts adicionados ao provedor de serviços do aplicativo. Para obter uma lista de todos os tipos de contexto, temos tudo registrado como
DbContextOptions
e analisamos a propriedade ContextType. (Veja abaixo como obtemos o provedor de serviços de aplicativo.) - Tipos derivados de DbContext nos assemblies de inicialização e de destino
Também usamos várias maneiras de instanciar o tipo. Aqui estão eles em ordem de precedência.
- Usando uma implementação de IDesignTimeDbContextFactory<T>
- Usando um IDbContextFactory<T> do provedor de serviços de aplicativos
- Usando ActivatorUtilities.CreateInstance
Localizando serviços de aplicativo
Para obter a fidelidade mais alta ao comportamento de runtime, tentamos obter a instância DbContext diretamente do provedor de serviços de aplicativo. Compartilhamos essa lógica com as ferramentas do ASP.NET Core. Sua manutenção faz parte do projeto dotnet/runtime no GitHub, no diretório Microsoft.Extensions.HostFactoryResolver.
Em poucas palavras, aqui estão algumas das estratégias que ele usa.
- Procure um método chamado BuildWebHost, CreateWebHostBuilder ou CreateHostBuilder ao lado do ponto de entrada do assembly
- Criar o host e obter os serviços da propriedade Serviços
- Chamar o ponto de entrada do assembly
- Interceptar os serviços durante a compilação do host e terminar antes de realmente iniciar o host
Serviços no tempo de design
Além dos serviços do aplicativo e dos serviços internos do DbContext, existe um terceiro conjunto de serviços em tempo de design. Elas não são adicionadas ao provedor de serviços interno, pois nunca são necessárias no runtime. Os serviços do tempo de design são criados pelo DesignTimeServicesBuilder. Há dois caminhos principais: um com uma instância de contexto e outro sem. O que não tem é usado principalmente quando se está montando um novo DbContext. Há vários pontos de extensibilidade aqui para permitir que o usuário, provedores e extensões substituam e personalizem os serviços.
O usuário pode personalizar serviços adicionando uma implementação de IDesignTimeServices
ao assembly de inicialização.
Os provedores podem personalizar os serviços adicionando o atributo DesignTimeProviderServices
ao conjunto. Isso aponta para uma implementação de IDesignTimeServices.
As extensões podem personalizar os serviços adicionando atributos DesignTimeServicesReference
ao assembly de destino ou de inicialização. Se o atributo especificar um provedor, ele só será adicionado quando esse provedor estiver em uso.
Registro de logs e exceções
Após criar uma instância do DbContext, conectaremos seu registro em log à saída da ferramenta. Isso permite que a saída seja gerada a partir do runtime. Todas as exceções sem tratamento também serão gravadas na saída. Existe um tipo de exceção especial OperationException
que pode ser gerado para encerrar suavemente as ferramentas e mostrar uma mensagem de erro simples sem um rastreamento de pilha.