Partilhar via


O modelo de provedor do Entity Framework 6

O modelo de provedor do Entity Framework permite que o Entity Framework seja usado com diferentes tipos de servidor de banco de dados. Por exemplo, um provedor pode ser conectado para permitir que o EF seja usado no Microsoft SQL Server, enquanto outro provedor pode ser conectado para permitir que o EF seja usado no Microsoft SQL Server Compact Edition. Os provedores para o EF6 que conhecemos podem ser encontrados na página provedores do Entity Framework.

Determinadas alterações na maneira como o EF interage com os provedores foram necessárias para permitir que o EF seja lançado sob uma licença de software livre. Essas alterações exigem a recriação de provedores do EF em relação aos assemblies EF6, juntamente com novos mecanismos de registro do provedor.

Recompilação

Com o EF6, o código principal que anteriormente fazia parte do .NET Framework agora está sendo enviado como assemblies OOB (fora de banda). Detalhes sobre como criar aplicativos no EF6 podem ser encontrados na página Atualizando aplicativos para EF6. Os provedores também precisarão ser recriados usando essas instruções.

Visão geral dos tipos de provedor

Um provedor de EF é na realidade uma coleção de serviços específicos de provedor definidos por tipos CLR a partir dos quais esses serviços se estendem (para uma classe base) ou são implementados (para uma interface). Dois desses serviços são fundamentais e necessários para que o EF funcione. Outros são opcionais e só precisam ser implementados se a funcionalidade específica for necessária e/ou as implementações padrão desses serviços não funcionarem para o servidor de banco de dados específico ao qual está sendo direcionado.

Tipos de provedor fundamentais

DbProviderFactory

O EF depende de ter um tipo derivado de System.Data.Common.DbProviderFactory para executar todo o acesso de banco de dados de baixo nível. DbProviderFactory não faz parte do EF, mas é uma classe no .NET Framework que serve um ponto de entrada para provedores de ADO.NET que podem ser usados pelo EF, por outros O/RMs ou diretamente por um aplicativo para obter instâncias de conexões, comandos, parâmetros e outras abstrações ADO.NET de maneira independente do provedor. Mais informações sobre DbProviderFactory podem ser encontradas na documentação do MSDN para ADO.NET.

DbProviderServices

O EF depende de ter um tipo derivado de DbProviderServices para fornecer uma funcionalidade adicional requerida pelo EF além da funcionalidade já fornecida pelo provedor do ADO.NET. Em versões mais antigas do EF, a classe DbProviderServices fazia parte do .NET Framework e era encontrada no namespace System.Data.Common. A partir do EF6, essa classe agora faz parte do EntityFramework.dll e está no namespace System.Data.Entity.Core.Common.

Mais detalhes sobre a funcionalidade fundamental de uma implementação do DbProviderServices podem ser encontrados no MSDN. No entanto, observe que, a partir do momento da gravação, essas informações não são atualizadas para o EF6, embora a maioria dos conceitos ainda seja válida. As implementações do SQL Server e do SQL Server Compact do DbProviderServices também são verificadas na base de código de software livre e podem servir como referências úteis para outras implementações.

Em versões mais antigas do EF, a implementação do DbProviderServices a ser usada foi obtida diretamente de um provedor de ADO.NET. Isso foi feito por meio da conversão do DbProviderFactory para IServiceProvider e da chamada do método GetService. Isso acoplou firmemente o provedor de EF ao DbProviderFactory. Esse acoplamento impediu que o EF fosse movido para fora do .NET Framework e, portanto, para o EF6 esse acoplamento firme foi removido e uma implementação do DbProviderServices agora está registrada diretamente no arquivo de configuração do aplicativo ou na configuração baseada em código, conforme descrito com mais detalhes na seção Registrando DbProviderServices abaixo.

Serviços adicionais

Além dos serviços fundamentais descritos acima, também há muitos outros serviços usados pelo EF que são sempre ou às vezes específicos do provedor. Implementações específicas do provedor padrão desses serviços podem ser fornecidas por uma implementação do DbProviderServices. Os aplicativos também podem substituir as implementações desses serviços ou fornecer implementações quando um tipo de DbProviderServices não fornece um padrão. Isso é descrito com mais detalhes na seção Resolvendo serviços adicionais abaixo.

Os tipos de serviço adicionais que podem ser de interesse para um provedor são listados abaixo. Mais detalhes sobre cada um desses tipos de serviço podem ser encontrados na documentação da API.

IDbExecutionStrategy

Esse é um serviço opcional que permite que um provedor implemente novas tentativas ou outro comportamento quando consultas e comandos são executados no banco de dados. Se nenhuma implementação for fornecida, o EF simplesmente executará os comandos e propagará as exceções geradas. Para o SQL Server, esse serviço é usado para fornecer uma política de repetição que é especialmente útil quando executado em servidores de banco de dados baseados em nuvem, como o SQL Azure.

IDbConnectionFactory

Esse é um serviço opcional que permite que um provedor crie objetos DbConnection por convenção quando é fornecido a ele apenas um nome de banco de dados. Observe que, embora esse serviço possa ser resolvido por uma implementação do DbProviderServices, ele está presente desde o EF 4.1 e também pode ser definido explicitamente no arquivo de configuração ou no código. O provedor só terá a chance de resolver esse serviço se ele estiver registrado como o provedor padrão (consulte O provedor padrão abaixo) e se um alocador de conexões padrão não tiver sido definido em outro lugar.

DbSpatialServices

Esse é um serviço opcional que permite que um provedor adicione suporte para tipos espaciais de geografia e geometria. Uma implementação desse serviço deve ser fornecida para que um aplicativo use o EF com tipos espaciais. DbSpatialServices é solicitado de duas maneiras. Primeiro, os serviços espaciais específicos do provedor são solicitados usando um objeto DbProviderInfo (que contém nome invariável e token de manifesto) como chave. Em segundo lugar, DbSpatialServices pode ser solicitado sem chave. Isso é usado para resolver o "provedor espacial global" usado ao criar tipos DbGeography ou DbGeometry autônomos.

MigrationSqlGenerator

Esse é um serviço opcional que permite que as Migrações de EF sejam usadas para a geração de SQL usada na criação e modificação de esquemas de banco de dados pelo Code First. Uma implementação é necessária para dar suporte a Migrações. Se uma implementação for fornecida, ela também será usada quando os bancos de dados forem criados usando inicializadores de banco de dados ou o método Database.Create.

Func<DbConnection, string, HistoryContextFactory>

Esse é um serviço opcional que permite que um provedor configure o mapeamento do HistoryContext para a tabela __MigrationHistory usada pelas Migrações de EF. O HistoryContext é um DbContext do Code First e pode ser configurado usando a API fluente normal para alterar coisas como o nome da tabela e as especificações de mapeamento de coluna. A implementação padrão desse serviço retornado pelo EF para todos os provedores poderá funcionar para um determinado servidor de banco de dados se todos os mapeamentos de tabela e coluna padrão tiverem suporte por esse provedor. Nesse caso, o provedor não precisa fornecer uma implementação desse serviço.

IDbProviderFactoryResolver

Esse é um serviço opcional para obter o DbProviderFactory correto de um determinado objeto DbConnection. A implementação padrão desse serviço retornado pelo EF para todos os provedores é planejada para funcionar para todos os provedores. No entanto, ao ser executado no .NET 4, o DbProviderFactory não estará publicamente acessível de um dos seus DbConnections. Portanto, o EF usa algumas heurísticas para pesquisar os provedores registrados para encontrar uma correspondência. É possível que, para alguns provedores, essas heurísticas falhem e, nessas situações, o provedor deve fornecer uma nova implementação.

Registrando DbProviderServices

A implementação do DbProviderServices a ser usada pode ser registrada no arquivo de configuração do aplicativo (app.config ou web.config) ou usando a configuração baseada em código. Em ambos os casos, o registro usa o "nome invariável" do provedor como uma chave. Isso permite que vários provedores sejam registrados e usados em um único aplicativo. O nome invariável usado para registros do EF é o mesmo que o nome invariável usado para as cadeias de conexão e registro do provedor do ADO.NET. Por exemplo, para o SQL Server, o nome invariável "System.Data.SqlClient" é usado.

Registro do arquivo de configuração

O tipo de DbProviderServices a ser usado é registrado como um elemento de provedor na lista de provedores da seção entityFramework do arquivo de configuração do aplicativo. Por exemplo:

<entityFramework>
  <providers>
    <provider invariantName="My.Invariant.Name" type="MyProvider.MyProviderServices, MyAssembly" />
  </providers>
</entityFramework>

A cadeia de caracteres do tipo deve ser o nome do tipo qualificado para assembly da implementação de DbProviderServices a ser usada.

Registro baseado em código

A partir do EF6, os provedores também podem ser registrados usando código. Isso permite que um provedor de EF seja usado sem nenhuma alteração no arquivo de configuração do aplicativo. Para usar a configuração baseada em código, um aplicativo deve criar uma classe DbConfiguration, conforme descrito na documentação de configuração baseada em código. O construtor da classe DbConfiguration deve chamar SetProviderServices para registrar o provedor de EF. Por exemplo:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetProviderServices("My.New.Provider", new MyProviderServices());
    }
}

Resolvendo serviços adicionais

Conforme mencionado acima na seção Visão geral de tipos de provedor, uma classe DbProviderServices também pode ser usada para resolver serviços adicionais. Isso é possível porque dbProviderServices implementa IDbDependencyResolver e cada tipo de DbProviderServices registrado é adicionado como um "resolvedor padrão". O mecanismo IDbDependencyResolver é descrito mais detalhadamente em Resolução de dependência. No entanto, não é necessário entender todos os conceitos nessa especificação para resolver serviços adicionais em um provedor.

A maneira mais comum para um provedor resolver serviços adicionais é chamar DbProviderServices.AddDependencyResolver para cada serviço no construtor da classe DbProviderServices. Por exemplo, SqlProviderServices (o provedor de EF para SQL Server) tem um código semelhante a este para inicialização:

private SqlProviderServices()
{
    AddDependencyResolver(new SingletonDependencyResolver<IDbConnectionFactory>(
        new SqlConnectionFactory()));

    AddDependencyResolver(new ExecutionStrategyResolver<DefaultSqlExecutionStrategy>(
        "System.data.SqlClient", null, () => new DefaultSqlExecutionStrategy()));

    AddDependencyResolver(new SingletonDependencyResolver<Func<MigrationSqlGenerator>>(
        () => new SqlServerMigrationSqlGenerator(), "System.data.SqlClient"));

    AddDependencyResolver(new SingletonDependencyResolver<DbSpatialServices>(
        SqlSpatialServices.Instance,
        k =>
        {
            var asSpatialKey = k as DbProviderInfo;
            return asSpatialKey == null
                || asSpatialKey.ProviderInvariantName == ProviderInvariantName;
        }));
}

Este construtor usa as seguintes classes auxiliares:

  • SingletonDependencyResolver: fornece uma maneira simples de resolver serviços Singleton, ou seja, serviços para os quais a mesma instância é retornada sempre que GetService é chamado. Os serviços transitórios geralmente são registrados como um alocador singleton que será usado para criar instâncias transitórias sob demanda.
  • ExecutionStrategyResolver: um resolvedor específico para retornar implementações IExecutionStrategy.

Em vez de usar DbProviderServices.AddDependencyResolver, também é possível substituir DbProviderServices.GetService e resolver serviços adicionais diretamente. Esse método será chamado quando o EF precisar de um serviço definido por um determinado tipo e, em alguns casos, para uma determinada chave. O método deverá retornar o serviço se puder ou retornar nulo para recusar o retorno do serviço e, em vez disso, permitir que outra classe o resolva. Por exemplo, para resolver o alocador de conexões padrão, o código em GetService pode ter esta aparência:

public override object GetService(Type type, object key)
{
    if (type == typeof(IDbConnectionFactory))
    {
        return new SqlConnectionFactory();
    }
    return null;
}

Ordem de registro

Quando várias implementações de DbProviderServices forem registradas no arquivo de configuração de um aplicativo, elas serão adicionadas como resolvedores secundários na ordem em que estão listadas. Como os resolvedores são sempre adicionados à parte superior da cadeia de resolvedores secundária, isso significa que o provedor no final da lista terá a chance de resolver dependências antes dos outros. (Isso pode parecer um pouco contra-intuitivo no início, mas faz sentido se você imaginar tirar cada provedor da lista e empilhá-lo em cima dos provedores existentes.)

Essa ordenação geralmente não importa porque a maioria dos serviços do provedor são específicos do provedor e são chaveados pelo nome invariável do provedor. No entanto, para serviços que não são chaveados pelo nome invariável do provedor ou alguma outra chave específica do provedor, o serviço será resolvido com base nessa ordenação. Por exemplo, se ele não for definido explicitamente de forma diferente em outro lugar, o alocador de conexões padrão virá do provedor mais alto da cadeia.

Registros de arquivo de configuração adicionais

É possível registrar explicitamente alguns dos serviços de provedor adicionais descritos acima diretamente no arquivo de configuração de um aplicativo. Quando isso for feito, o registro no arquivo de configuração será usado em vez de qualquer coisa retornada pelo método GetService da implementação do DbProviderServices.

Registrando o alocador de conexões padrão

A partir do EF5, o pacote NuGet do EntityFramework registra automaticamente o alocador de conexões do SQL Express ou o alocador de conexões LocalDb no arquivo de configuração.

Por exemplo:

<entityFramework>
  <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" >
</entityFramework>

O tipo é o nome de tipo qualificado pelo assembly para o alocador de conexões padrão, que deve implementar o IDbConnectionFactory.

É recomendável que um pacote NuGet do provedor defina o alocador de conexões padrão dessa maneira quando instalado. Veja os Pacotes NuGet para provedores abaixo.

Alterações adicionais do provedor do EF6

Alterações no provedor espacial

Os provedores que dão suporte a tipos espaciais agora devem implementar alguns métodos adicionais em classes derivadas de DbSpatialDataReader:

  • public abstract bool IsGeographyColumn(int ordinal)
  • public abstract bool IsGeometryColumn(int ordinal)

Há também novas versões assíncronas de métodos existentes que são recomendadas para serem substituídas como representante de implementações padrão para os métodos síncronos e, portanto, não são executadas de forma assíncrona:

  • public virtual Task<DbGeography> GetGeographyAsync(int ordinal, CancellationToken cancellationToken)
  • public virtual Task<DbGeometry> GetGeometryAsync(int ordinal, CancellationToken cancellationToken)

Suporte nativo para Enumerable.Contains

O EF6 apresenta um novo tipo de expressão, DbInExpression, que foi adicionado para resolver problemas de desempenho em torno do uso de Enumerable.Contains em consultas LINQ. A classe DbProviderManifest tem um novo método virtual, SupportsInExpression, que é chamado pelo EF para determinar se um provedor lida com o novo tipo de expressão. Para compatibilidade com implementações de provedor existentes, o método retorna false. Para se beneficiar dessa melhoria, um provedor de EF6 pode adicionar código para manipular o DbInExpression e substituir SupportsInExpression para retornar true. Uma instância de DbInExpression pode ser criada chamando o método DbExpressionBuilder.In. Uma instância DbInExpression é composta por uma DbExpression, geralmente representando uma coluna de tabela e uma lista de DbConstantExpression para testar uma correspondência.

Pacotes NuGet para provedores

Uma maneira de disponibilizar um provedor de EF6 é lançá-lo como um pacote NuGet. O uso de um pacote NuGet tem as seguintes vantagens:

  • É fácil usar o NuGet para adicionar o registro do provedor ao arquivo de configuração do aplicativo
  • Alterações adicionais podem ser feitas no arquivo de configuração para definir o locador de conexões padrão para que as conexões feitas por convenção usem o provedor registrado
  • O NuGet manipula a adição de redirecionamentos de associação para que o provedor do EF6 continue funcionando mesmo após o lançamento de um novo pacote EF

Um exemplo disso é o pacote EntityFramework.SqlServerCompact incluído na base de código de software livre. Esse pacote fornece um bom modelo para a criação de pacotes NuGet de provedores de EF.

Comandos do PowerShell

Quando o pacote NuGet do EntityFramework é instalado, ele registra um módulo do PowerShell que contém dois comandos que são muito úteis para pacotes de provedor:

  • Add-EFProvider adiciona uma nova entidade para o provedor no arquivo de configuração do projeto de destino e garante que ele esteja no final da lista de provedores registrados.
  • Add-EFDefaultConnectionFactory adiciona ou atualiza o registro do defaultConnectionFactory no arquivo de configuração do projeto de destino.

Ambos os comandos cuidam da adição de uma seção do entityFramework ao arquivo de configuração e da adição de uma coleção de provedores, se necessário.

Pretende-se que esses comandos sejam chamados do script NuGet install.ps1. Por exemplo, install.ps1 para o provedor do SQL Compact é semelhante a este:

param($installPath, $toolsPath, $package, $project)
Add-EFDefaultConnectionFactory $project 'System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework' -ConstructorArguments 'System.Data.SqlServerCe.4.0'
Add-EFProvider $project 'System.Data.SqlServerCe.4.0' 'System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact'</pre>

Mais informações sobre esses comandos podem ser obtidas usando a ajuda na janela Console do Gerenciador de Pacotes.

Encapsulando provedores

Um provedor de encapsulamento é um provedor de EF e/ou do ADO.NET que encapsula um provedor existente para estendê-lo com outras funcionalidades, como criação de perfil ou recursos de rastreamento. Os provedores de encapsulamento podem ser registrados da maneira normal, mas geralmente é mais conveniente configurar o provedor de encapsulamento no runtime interceptando a resolução de serviços relacionados ao provedor. O evento estático OnLockingConfiguration na classe DbConfiguration pode ser usado para fazer isso.

OnLockingConfiguration é chamado após o EF determinar de onde toda a configuração de EF para o domínio do aplicativo será obtida, mas antes de ser bloqueada para uso. Na inicialização do aplicativo (antes do EF ser usado), o aplicativo deve registrar um manipulador de eventos para esse evento. (Estamos considerando adicionar suporte para registrar esse manipulador no arquivo de configuração, mas isso ainda não tem suporte.) Em seguida, o manipulador de eventos deve fazer uma chamada para ReplaceService para cada serviço que precisa ser encapsulado.

Por exemplo, para encapsular IDbConnectionFactory e DbProviderService, um manipulador semelhante a este deve ser registrado:

DbConfiguration.OnLockingConfiguration +=
    (_, a) =>
    {
        a.ReplaceService<DbProviderServices>(
            (s, k) => new MyWrappedProviderServices(s));

        a.ReplaceService<IDbConnectionFactory>(
            (s, k) => new MyWrappedConnectionFactory(s));
    };

O serviço que foi resolvido e agora deve ser encapsulado com a chave que foi usada para resolver o serviço é passado para o manipulador. Em seguida, o manipulador pode encapsular esse serviço e substituir o serviço retornado pela versão encapsulada.

Resolvendo um DbProviderFactory com o EF

DbProviderFactory é um dos tipos de provedores fundamentais necessários para o EF, conforme descrito na seção Visão geral de tipos de provedor vista acima. Como já mencionado, não é um tipo de EF e o registro geralmente não faz parte da configuração do EF. Em vez disso, é feito o registro normal do provedor do ADO.NET no arquivo machine.config e/ou no arquivo de configuração do aplicativo.

Apesar disso, o EF ainda usa seu mecanismo normal de resolução de dependência ao procurar um DbProviderFactory para usar. O resolvedor padrão usa o registro normal do ADO.NET nos arquivos de configuração e, portanto, isso geralmente é transparente. Mas devido ao mecanismo normal de resolução de dependência ser usado, isso significa que um IDbDependencyResolver pode ser usado para resolver um DbProviderFactory mesmo quando o registro de ADO.NET normal não tiver sido feito.

Resolver dbProviderFactory dessa forma tem várias implicações:

  • Um aplicativo que usa a configuração baseada em código pode adicionar chamadas em sua classe DbConfiguration para registrar o DbProviderFactory apropriado. Isso é especialmente útil para aplicativos que não desejam (ou não podem) usar nenhuma configuração baseada em arquivo.
  • O serviço pode ser encapsulado ou substituído usando ReplaceService, conforme descrito na seção Encapsulamento de provedores vista acima
  • Teoricamente, uma implementação de DbProviderServices poderia resolver um DbProviderFactory.

O ponto importante a observar sobre como fazer qualquer uma dessas coisas é que elas afetarão apenas a pesquisa de DbProviderFactory pelo EF. Outro código não EF ainda pode pressupor que o provedor de ADO.NET seja registrado da maneira normal e poderá falhar se o registro não for encontrado. Por esse motivo, normalmente é melhor que um DbProviderFactory seja registrado da maneira normal do ADO.NET.

Se o EF for usado para resolver um DbProviderFactory, ele também deverá resolver os serviços IProviderInvariantName e IDbProviderFactoryResolver.

IProviderInvariantName é um serviço usado para determinar um nome invariável do provedor para um determinado tipo de DbProviderFactory. A implementação padrão desse serviço usa o registro do provedor de ADO.NET. Isso significa que, se o provedor de ADO.NET não estiver registrado da maneira normal porque o dbProviderFactory está sendo resolvido pelo EF, também será necessário resolver esse serviço. Observe que um resolvedor para esse serviço é adicionado automaticamente ao usar o método DbConfiguration.SetProviderFactory.

Conforme descrito na seção Visão geral de tipos de provedor acima, o IDbProviderFactoryResolver é usado para obter o DbProviderFactory correto de um determinado objeto DbConnection. A implementação padrão desse serviço, ao ser executado no .NET 4, usa o registro do provedor de ADO.NET. Isso significa que, se o provedor de ADO.NET não estiver registrado da maneira normal porque o dbProviderFactory está sendo resolvido pelo EF, também será necessário resolver esse serviço.