Condividi tramite


Modello di provider Entity Framework 6

Il modello di provider Entity Framework consente l'uso di Entity Framework con diversi tipi di server di database. Ad esempio, un provider può essere collegato per consentire l'uso di ENTITY in Microsoft SQL Server, mentre un altro provider può essere collegato per consentire l'uso di ENTITY in Microsoft SQL Server Compact Edition. I provider per EF6 a cui si è a conoscenza sono disponibili nella pagina provider di Entity Framework.

Alcune modifiche sono state necessarie per il modo in cui Entity Framework interagisce con i provider per consentire il rilascio di Entity Framework con una licenza open source. Queste modifiche richiedono la ricompilazione dei provider EF rispetto agli assembly EF6 insieme ai nuovi meccanismi per la registrazione del provider.

Rifacimento

Con EF6 il codice di base che in precedenza faceva parte di .NET Framework viene ora fornito come assembly fuori banda (OOB). Per informazioni dettagliate su come compilare applicazioni con EF6, vedere la pagina Aggiornamento delle applicazioni per EF6 . I provider dovranno anche essere ricompilati usando queste istruzioni.

Panoramica dei tipi di provider

Un provider EF è in realtà una raccolta di servizi specifici del provider definiti dai tipi CLR che questi servizi si estendono da (per una classe base) o implementano (per un'interfaccia). Due di questi servizi sono fondamentali e necessari per il funzionamento di EF. Altri sono facoltativi e devono essere implementati solo se sono necessarie funzionalità specifiche e/o le implementazioni predefinite di questi servizi non funzionano per il server di database specifico di destinazione.

Tipi di provider fondamentali

DbProviderFactory

Ef dipende dalla presenza di un tipo derivato da System.Data.Common.DbProviderFactory per l'esecuzione di tutti gli accessi di database di basso livello. DbProviderFactory non fa in realtà parte di EF, ma è invece una classe in .NET Framework che serve un punto di ingresso per i provider di ADO.NET che possono essere usati da ENTITY, da altri O/RMs o direttamente da un'applicazione per ottenere istanze di connessioni, comandi, parametri e altre astrazioni ADO.NET in modo indipendente dal provider. Altre informazioni su DbProviderFactory sono disponibili nella documentazione MSDN per ADO.NET.

DbProviderServices

Entity Framework dipende dalla presenza di un tipo derivato da DbProviderServices per fornire funzionalità aggiuntive necessarie da Entity Framework oltre alle funzionalità già fornite dal provider di ADO.NET. Nelle versioni precedenti di EF la classe DbProviderServices faceva parte di .NET Framework ed è stata trovata nello spazio dei nomi System.Data.Common. A partire da EF6 questa classe fa ora parte di EntityFramework.dll ed è nello spazio dei nomi System.Data.Entity.Core.Common.

Altre informazioni sulle funzionalità fondamentali di un'implementazione di DbProviderServices sono disponibili in MSDN. Si noti tuttavia che a partire dal momento della scrittura di queste informazioni non viene aggiornato per EF6 anche se la maggior parte dei concetti è ancora valida. Anche le implementazioni di SQL Server e SQL Server Compact di DbProviderServices vengono archiviate nella codebase open source e possono fungere da riferimenti utili per altre implementazioni.

Nelle versioni precedenti di EF l'implementazione dbProviderServices da usare è stata ottenuta direttamente da un provider di ADO.NET. Questa operazione è stata eseguita eseguendo il cast di DbProviderFactory a IServiceProvider e chiamando il metodo GetService. In questo modo il provider EF è strettamente associato a DbProviderFactory. Questo accoppiamento ha impedito lo spostamento di EF da .NET Framework e pertanto per EF6 questo accoppiamento stretto è stato rimosso e un'implementazione di DbProviderServices è ora registrata direttamente nel file di configurazione dell'applicazione o nella configurazione basata su codice, come descritto in dettaglio nella sezione Registrazione di DbProviderServices di seguito.

Servizi aggiuntivi

Oltre ai servizi fondamentali descritti in precedenza, esistono anche molti altri servizi usati da EF, che sono sempre o talvolta specifici del provider. Le implementazioni predefinite specifiche del provider di questi servizi possono essere fornite da un'implementazione dbProviderServices. Le applicazioni possono anche eseguire l'override delle implementazioni di questi servizi o fornire implementazioni quando un tipo DbProviderServices non fornisce un valore predefinito. Questo articolo è descritto in modo più dettagliato nella sezione Risoluzione di servizi aggiuntivi di seguito.

Di seguito sono elencati i tipi di servizio aggiuntivi che un provider potrebbe interessare a un provider. Altri dettagli su ognuno di questi tipi di servizio sono disponibili nella documentazione dell'API.

IDbExecutionStrategy

Si tratta di un servizio facoltativo che consente a un provider di implementare nuovi tentativi o altro comportamento quando le query e i comandi vengono eseguiti sul database. Se non viene fornita alcuna implementazione, Entity Framework eseguirà semplicemente i comandi e propaga tutte le eccezioni generate. Per SQL Server questo servizio viene usato per fornire criteri di ripetizione dei tentativi particolarmente utili quando vengono eseguiti su server di database basati sul cloud, ad esempio SQL Azure.

IDbConnectionFactory

Si tratta di un servizio facoltativo che consente a un provider di creare oggetti DbConnection per convenzione quando viene specificato solo un nome di database. Si noti che, sebbene questo servizio possa essere risolto da un'implementazione dbProviderServices, è presente a partire da EF 4.1 e può essere impostato in modo esplicito nel file di configurazione o nel codice. Il provider avrà la possibilità di risolvere questo servizio solo se è stato registrato come provider predefinito (vedere Il provider predefinito seguente) e se una factory di connessione predefinita non è stata impostata altrove.

DbSpatialServices

Si tratta di servizi facoltativi che consentono a un provider di aggiungere il supporto per i tipi geografici e spaziali geometry. È necessario fornire un'implementazione di questo servizio per consentire a un'applicazione di usare Entity Framework con tipi spaziali. DbSpatialServices viene richiesto in due modi. Per prima cosa, i servizi spaziali specifici del provider vengono richiesti usando un oggetto DbProviderInfo (che contiene il nome invariante e il token manifesto) come chiave. In secondo luogo, è possibile richiedere DbSpatialServices senza chiave. Viene usato per risolvere il "provider spaziale globale" usato durante la creazione di tipi DbGeography autonomi o DbGeometry.

MigrationSqlGenerator

Si tratta di un servizio facoltativo che consente l'uso delle migrazioni di Entity Framework per la generazione di SQL usata per la creazione e la modifica di schemi di database da Code First. Per supportare le migrazioni, è necessaria un'implementazione. Se viene fornita un'implementazione, verrà usata anche quando i database vengono creati usando gli inizializzatori di database o il metodo Database.Create.

Func<DbConnection, string, HistoryContextFactory>

Si tratta di un servizio facoltativo che consente a un provider di configurare il mapping di HistoryContext alla __MigrationHistory tabella usata dalle migrazioni di Entity Framework. HistoryContext è un DbContext Code First e può essere configurato usando la normale API Fluent per modificare elementi come il nome della tabella e le specifiche di mapping delle colonne. L'implementazione predefinita di questo servizio restituito da ENTITY per tutti i provider può funzionare per un determinato server di database se tutti i mapping predefiniti di tabelle e colonne sono supportati da tale provider. In tal caso, il provider non deve fornire un'implementazione del servizio.

IDbProviderFactoryResolver

Si tratta di un servizio facoltativo per ottenere l'oggetto DbProviderFactory corretto da un determinato oggetto DbConnection. L'implementazione predefinita di questo servizio restituito da ENTITY per tutti i provider è destinata a funzionare per tutti i provider. Tuttavia, quando si esegue in .NET 4, DbProviderFactory non è accessibile pubblicamente da uno se dbConnections. Ef usa quindi alcune euristiche per cercare nei provider registrati una corrispondenza. È possibile che per alcuni provider questi euristici avranno esito negativo e in tali situazioni il provider dovrebbe fornire una nuova implementazione.

Registrazione di DbProviderServices

L'implementazione di DbProviderServices da usare può essere registrata nel file di configurazione dell'applicazione (app.config o web.config) o usando la configurazione basata su codice. In entrambi i casi, la registrazione usa il "nome invariante" del provider come chiave. Ciò consente la registrazione e l'uso di più provider in una singola applicazione. Il nome invariante usato per le registrazioni ef corrisponde al nome invariante usato per la registrazione del provider ADO.NET e stringa di connessione. Ad esempio, per SQL Server viene usato il nome invariante "System.Data.SqlClient".

Registrazione nel file di configurazione

Il tipo DbProviderServices da usare viene registrato come elemento del provider nell'elenco dei provider della sezione entityFramework del file di configurazione dell'applicazione. Ad esempio:

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

La stringa di tipo deve essere il nome del tipo qualificato dall'assembly dell'implementazione dbProviderServices da usare.

Registrazione basata su codice

A partire dai provider EF6 è anche possibile registrare usando il codice. In questo modo un provider EF può essere usato senza alcuna modifica al file di configurazione dell'applicazione. Per usare la configurazione basata su codice, un'applicazione deve creare una classe DbConfiguration come descritto nella documentazione sulla configurazione basata su codice. Il costruttore della classe DbConfiguration deve quindi chiamare SetProviderServices per registrare il provider EF. Ad esempio:

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

Risoluzione di servizi aggiuntivi

Come indicato in precedenza nella sezione Panoramica dei tipi di provider, è anche possibile usare una classe DbProviderServices per risolvere servizi aggiuntivi. Ciò è possibile perché DbProviderServices implementa IDbDependencyResolver e ogni tipo DbProviderServices registrato viene aggiunto come "resolver predefinito". Il meccanismo IDbDependencyResolver è descritto in modo più dettagliato in Risoluzione delle dipendenze. Tuttavia, non è necessario comprendere tutti i concetti di questa specifica per risolvere servizi aggiuntivi in un provider.

Il modo più comune per risolvere altri servizi è chiamare DbProviderServices.AddDependencyResolver per ogni servizio nel costruttore della classe DbProviderServices. Ad esempio, SqlProviderServices (il provider EF per SQL Server) ha codice simile al seguente per l'inizializzazione:

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;
        }));
}

Questo costruttore usa le classi helper seguenti:

  • SingletonDependencyResolver: offre un modo semplice per risolvere i servizi Singleton, ovvero i servizi per i quali viene restituita la stessa istanza ogni volta che viene chiamato GetService. I servizi temporanei vengono spesso registrati come factory singleton che verranno usati per creare istanze temporanee su richiesta.
  • ExecutionStrategyResolver: un resolver specifico per la restituzione di implementazioni IExecutionStrategy.

Anziché usare DbProviderServices.AddDependencyResolver, è anche possibile eseguire l'override di DbProviderServices.GetService e risolvere direttamente altri servizi. Questo metodo verrà chiamato quando ENTITY necessita di un servizio definito da un determinato tipo e, in alcuni casi, per una determinata chiave. Il metodo deve restituire il servizio, se possibile, o restituire null per rifiutare esplicitamente la restituzione del servizio e consentire a un'altra classe di risolverlo. Ad esempio, per risolvere la factory di connessione predefinita, il codice in GetService potrebbe essere simile al seguente:

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

Ordine di registrazione

Quando più implementazioni dbProviderServices vengono registrate nel file di configurazione di un'applicazione, verranno aggiunte come resolver secondari nell'ordine in cui sono elencate. Poiché i resolver vengono sempre aggiunti all'inizio della catena del resolver secondario, ciò significa che il provider alla fine dell'elenco otterrà la possibilità di risolvere le dipendenze prima delle altre. Questo può sembrare un po ' intuitivo in un primo momento, ma ha senso se si immagina di rimuovere ogni provider dall'elenco e impilarlo sopra i provider esistenti.

Questo ordinamento in genere non è importante perché la maggior parte dei servizi provider è specifica del provider e con chiave in base al nome invariante del provider. Tuttavia, per i servizi non con chiave per nome invariante del provider o per altre chiavi specifiche del provider, il servizio verrà risolto in base a questo ordinamento. Ad esempio, se non è impostata in modo esplicito in modo diverso in un'altra posizione, la factory di connessione predefinita proviene dal provider più in alto nella catena.

Registrazioni aggiuntive dei file di configurazione

È possibile registrare in modo esplicito alcuni dei servizi provider aggiuntivi descritti in precedenza direttamente nel file di configurazione di un'applicazione. Al termine, la registrazione nel file di configurazione verrà usata invece di qualsiasi elemento restituito dal metodo GetService dell'implementazione dbProviderServices.

Registrazione della factory di connessione predefinita

A partire da EF5, il pacchetto NuGet EntityFramework ha registrato automaticamente la factory di connessione SQL Express o la factory di connessione LocalDb nel file di configurazione.

Ad esempio:

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

Il tipo è il nome del tipo qualificato dall'assembly per la factory di connessione predefinita, che deve implementare IDbConnectionFactory.

È consigliabile che un pacchetto NuGet del provider imposti la factory di connessione predefinita in questo modo quando viene installato. Vedere Pacchetti NuGet per i provider di seguito.

Modifiche aggiuntive al provider EF6

Modifiche del provider spaziale

I provider che supportano i tipi spaziali devono ora implementare alcuni metodi aggiuntivi sulle classi derivate da DbSpatialDataReader:

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

Sono inoltre disponibili nuove versioni asincrone dei metodi esistenti che è consigliabile eseguire l'override come delegato delle implementazioni predefinite ai metodi sincroni e pertanto non vengono eseguite in modo asincrono:

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

Supporto nativo per Enumerable.Contains

EF6 introduce un nuovo tipo di espressione, DbInExpression, che è stato aggiunto per risolvere i problemi di prestazioni relativi all'uso di Enumerable.Contains nelle query LINQ. La classe DbProviderManifest dispone di un nuovo metodo virtuale SupportsInExpression, chiamato da EF per determinare se un provider gestisce il nuovo tipo di espressione. Per compatibilità con le implementazioni del provider esistenti, il metodo restituisce false. Per trarre vantaggio da questo miglioramento, un provider EF6 può aggiungere codice per gestire DbInExpression ed eseguire l'override di SupportsInExpression per restituire true. È possibile creare un'istanza di DbInExpression chiamando il metodo DbExpressionBuilder.In. Un'istanza dbInExpression è costituita da un oggetto DbExpression, che in genere rappresenta una colonna di tabella e un elenco di DbConstantExpression per verificare una corrispondenza.

Pacchetti NuGet per i provider

Un modo per rendere disponibile un provider EF6 consiste nel rilasciarlo come pacchetto NuGet. L'uso di un pacchetto NuGet presenta i vantaggi seguenti:

  • È facile usare NuGet per aggiungere la registrazione del provider al file di configurazione dell'applicazione
  • È possibile apportare modifiche aggiuntive al file di configurazione per impostare la factory di connessione predefinita in modo che le connessioni effettuate per convenzione usino il provider registrato
  • NuGet gestisce l'aggiunta di reindirizzamenti di binding in modo che il provider EF6 continui a funzionare anche dopo il rilascio di un nuovo pacchetto EF

Un esempio è il pacchetto EntityFramework.SqlServerCompact incluso nella codebase open source. Questo pacchetto offre un modello valido per la creazione di pacchetti NuGet del provider EF.

Comandi di PowerShell

Quando il pacchetto NuGet EntityFramework è installato, registra un modulo di PowerShell che contiene due comandi molto utili per i pacchetti del provider:

  • Add-EFProvider aggiunge una nuova entità per il provider nel file di configurazione del progetto di destinazione e verifica che sia alla fine dell'elenco dei provider registrati.
  • Add-EFDefaultConnectionFactory aggiunge o aggiorna la registrazione defaultConnectionFactory nel file di configurazione del progetto di destinazione.

Entrambi questi comandi si occupano dell'aggiunta di una sezione entityFramework al file di configurazione e dell'aggiunta di una raccolta di provider, se necessario.

È previsto che questi comandi vengano chiamati dallo script NuGet install.ps1. Ad esempio, install.ps1 per il provider SQL Compact è simile al seguente:

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>

Per ottenere altre informazioni su questi comandi, usare get-help nella finestra della console di Gestione pacchetti.

Wrapping dei provider

Un provider di wrapping è un provider ef e/o ADO.NET che esegue il wrapping di un provider esistente per estenderlo con altre funzionalità, ad esempio la profilatura o la traccia. I provider di wrapping possono essere registrati normalmente, ma spesso è più comodo configurare il provider di wrapping in fase di esecuzione intercettando la risoluzione dei servizi correlati al provider. L'evento statico OnLockingConfiguration nella classe DbConfiguration può essere usato per eseguire questa operazione.

OnLockingConfiguration viene chiamato dopo che EF ha determinato da dove verrà ottenuta tutta la configurazione di Entity Framework per il dominio dell'app, ma prima che venga bloccata per l'uso. All'avvio dell'app (prima dell'uso di Entity Framework) l'app deve registrare un gestore eventi per questo evento. Si sta valutando l'aggiunta del supporto per la registrazione di questo gestore nel file di configurazione, ma questa operazione non è ancora supportata. Il gestore eventi deve quindi effettuare una chiamata a ReplaceService per ogni servizio che deve essere sottoposto a wrapping.

Ad esempio, per eseguire il wrapping di IDbConnectionFactory e DbProviderService, è necessario registrare un gestore simile al seguente:

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

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

Il servizio risolto e che deve ora essere sottoposto a wrapping insieme alla chiave usata per risolvere il servizio vengono passati al gestore. Il gestore può quindi eseguire il wrapping del servizio e sostituire il servizio restituito con la versione di cui è stato eseguito il wrapping.

Risoluzione di un oggetto DbProviderFactory con ENTITY

DbProviderFactory è uno dei tipi di provider fondamentali necessari per EF, come descritto nella sezione Panoramica dei tipi di provider precedente. Come già accennato, non è un tipo ef e la registrazione in genere non fa parte della configurazione di Entity Framework, ma è invece la normale registrazione del provider ADO.NET nel file machine.config e/o nel file di configurazione dell'applicazione.

Nonostante questo framework usi ancora il normale meccanismo di risoluzione delle dipendenze quando si cerca un DbProviderFactory da usare. Il sistema di risoluzione predefinito usa la normale registrazione ADO.NET nei file di configurazione e pertanto è in genere trasparente. Tuttavia, a causa del normale meccanismo di risoluzione delle dipendenze viene usato significa che un IDbDependencyResolver può essere usato per risolvere un DbProviderFactory anche quando non è stata eseguita la registrazione normale ADO.NET.

La risoluzione di DbProviderFactory in questo modo ha diverse implicazioni:

  • Un'applicazione che usa la configurazione basata su codice può aggiungere chiamate nella classe DbConfiguration per registrare l'oggetto DbProviderFactory appropriato. Ciò è particolarmente utile per le applicazioni che non vogliono (o non possono) usare alcuna configurazione basata su file.
  • Il servizio può essere sottoposto a wrapping o sostituito usando ReplaceService, come descritto nella sezione Wrapping dei provider precedente
  • Teoricamente, un'implementazione di DbProviderServices potrebbe risolvere un dbProviderFactory.

Il punto importante da notare per eseguire una di queste operazioni è che influiscono solo sulla ricerca di DbProviderFactory da EF. Un altro codice non EF potrebbe comunque aspettarsi che il provider ADO.NET venga registrato nel modo normale e potrebbe non riuscire se la registrazione non viene trovata. Per questo motivo, in genere è preferibile registrare dbProviderFactory nel normale ADO.NET modo.

Se EF viene usato per risolvere un dbProviderFactory, deve risolvere anche i servizi IProviderInvariantName e IDbProviderFactoryResolver.

IProviderInvariantName è un servizio usato per determinare un nome invariante del provider per un determinato tipo di DbProviderFactory. L'implementazione predefinita di questo servizio usa la registrazione del provider ADO.NET. Ciò significa che se il provider di ADO.NET non è registrato nel modo normale perché DbProviderFactory viene risolto da EF, sarà necessario risolvere anche questo servizio. Si noti che un resolver per questo servizio viene aggiunto automaticamente quando si usa il metodo DbConfiguration.SetProviderFactory.

Come descritto nella sezione Panoramica dei tipi di provider precedente, L'oggetto IDbProviderFactoryResolver viene usato per ottenere l'oggetto DbProviderFactory corretto da un determinato oggetto DbConnection. L'implementazione predefinita di questo servizio durante l'esecuzione in .NET 4 usa la registrazione del provider ADO.NET. Ciò significa che se il provider di ADO.NET non è registrato nel modo normale perché DbProviderFactory viene risolto da EF, sarà necessario risolvere anche questo servizio.