Entity Framework 6 プロバイダー モデル

Entity Framework プロバイダー モデルを使用すると、Entity Framework を、異なる種類のデータベース サーバーと共に使用できます。 たとえば、EF を Microsoft SQL Server に対して使用できるように 1 つのプロバイダーを接続するのと同時に、EF を Microsoft SQL Server Compact Edition に対して使用できるように別のプロバイダーを接続することができます。 Microsoft が認識している EF6 プロバイダーは、Entity Framework プロバイダーに関するページで確認できます。

EF をオープン ソース ライセンスのもとでリリースできるように、EF がプロバイダーとやり取りする方法に対して、一定の変更が必要とされました。 これらの変更により、プロバイダー登録用の新しいメカニズムが一緒になった EF6 アセンブリに対する EF プロバイダーのリビルドが必要となっています。

再構築

EF6 では、以前は .NET Framework の一部であったコア コードが、帯域外 (OOB) アセンブリとして出荷されるようになっています。 EF6 に対してアプリケーションをビルドする方法の詳細については、EF6 用アプリケーションの更新に関するページを参照してください。 プロバイダーは、これらの指示に従ってリビルドを行う必要もあります。

プロバイダー型の概要

EF プロバイダーは、実際にはプロバイダー固有のサービスのコレクションであり、これらのサービスの拡張元 (基本クラスの場合)であるか実装 (インターフェイスの場合) である CLR 型によって定義されています。 EF が機能するためには、これらの 2 つのサービスが土台であり、必要とされます。 その他は省略可能であり、特定の機能が必要な場合や、これらのサービスの既定の実装が、対象となる特定のデータベース サーバーに対して機能しない場合にのみ実装する必要があります。

基本的なプロバイダー型

DbProviderFactory

EF では、すべての低レベルのデータベース アクセスを実行するために、System.Data.Common.DbProviderFactory からの派生型を用意する必要があります。 DbProviderFactory は、実際には EF の一部ではなく .NET Framework のクラスであり、プロバイダーに依存しない方法で接続、コマンド、パラメーター、その他の ADO.NET 抽象化のインスタンスを取得するために、EF を始めとする O/RM や、アプリケーションが直接使用できる ADO.NET プロバイダーのエントリ ポイントを提供します。 DbProviderFactory の詳細については、ADO.NET に関する MSDN ドキュメントを参照してください。

DbProviderServices

EF では、ADO.NET プロバイダーによって既に提供されている機能に加えて、EF が必要とする追加の機能を提供するため、DbProviderServices からの派生型を用意する必要があります。 以前のバージョンの EF では、DbProviderServices クラスは .NET Framework の一部であり、System.Data.Common 名前空間にありました。 EF6 より、このクラスは EntityFramework.dll の一部であり、System.Data.Entity.Core.Common 名前空間の中にあります。

DbProviderServices 実装の基本的な機能の詳細については、MSDN を参照してください。 ただし、この情報を記述した時点では、その概念のほとんどはまだ妥当ですが、EF6 向けの更新は行われていません。 DbProviderServices の SQL Server 実装と SQL Server Compact 実装は、オープンソースのコードベースにもチェックインされており、その他の実装にとって有用な参照として役立てることができます。

以前のバージョンの EF では、使用する DbProviderServices 実装は、ADO.NET プロバイダーから直接取得されました。 これは、DbProviderFactory を IServiceProvider にキャストし、GetService メソッドを呼び出すことで行われました。 これにより、EF プロバイダーが DbProviderFactory に緊密に結合されました。 この結合が、.NET Framework から EF を移動させることを妨げました。それゆえ EF6 では、この緊密な結合が解消されて、DbProviderServices の実装は、後の「DbProviderServices の登録」セクションで詳しく説明するように、アプリケーションの構成ファイルまたはコード ベースの構成に直接登録されるようになりました。

追加サービス

上記の基本的なサービスに加えて、EF で使用される、常に、または場合によってプロバイダー固有である他のサービスも多数存在します。 これらのサービスの、プロバイダー固有である既定の実装は、DbProviderServices 実装によって提供できます。 アプリケーションでは、これらのサービスの実装をオーバーライドしたり、DbProviderServices 型で既定値が提供されない場合に実装を提供したりすることもできます。 これについては、後の「追加サービスの解決」セクションで、より詳しく説明しています。

以下に、プロバイダーにとって重要な場合があるその他のサービス型を示します。 これらの各サービス型の詳細は、API ドキュメントに記載されています。

IDbExecutionStrategy

これは、データベースに対してクエリやコマンドが実行されたときに、再試行などの動作をプロバイダーが実装できるようにするオプション サービスです。 実装が指定されなかった場合は、EF により、単純にコマンドが実行されて、スローされた例外があれば伝達されます。 SQL Server の場合、このサービスを使用して再試行ポリシーを指定できます。これは特に、実行対象が SQL Azure などのクラウドベースのデータベース サーバーである場合に便利です。

IDbConnectionFactory

これは、データベース名のみが指定されたときに、規則に従ってプロバイダー側で DbConnection オブジェクトを作成できるようにするオプション サービスです。 このサービスは DbProviderServices 実装によって解決できますが、EF 4.1 以降は存在しており、構成ファイルまたはコードのいずれかで、明示的な設定も可能であることに注意してください。 プロバイダーがこのサービスを解決する機会を得ることになるのは、既定のプロバイダーとして登録されている場合 (後の「既定のプロバイダー」を参照) と、他の場所で既定の接続ファクトリが設定されていない場合のみです。

DbSpatialServices

これは、プロバイダーが空間型 geography および geometry のサポートを追加できるようにするオプション サービスです。 アプリケーションで空間型を含む EF を使用するには、このサービスの実装が提供される必要があります。 DbSptialServices の要求は 2 つの方法で行います。 1 つ目として、キーとして DbProviderInfo オブジェクト (インバリアント名とマニフェスト トークンを格納) を使用して、プロバイダー固有の空間サービスを要求します。 2 つ目は、キーを指定せずに DbSpatialServices を要求する方法です。 これは、スタンドアロンの DbGeography 型または DbGeometry 型を作成するときに使用される "グローバル空間プロバイダー" を解決するために使用されます。

MigrationSqlGenerator

これは、Code First によるデータベース スキーマの作成と変更で使用される SQL の生成のために、EF 移行を使用できるようにするオプション サービスです。 移行をサポートするためには、実装が必要です。 実装が提供されている場合、これはデータベースがデータベース初期化子または Database.Create メソッドを使用して作成されるときにも使用されます。

Func<DbConnection, string, HistoryContextFactory>

これは、プロバイダーが、EF 移行によって使用される __MigrationHistory テーブルへの HistoryContext のマッピングを構成できるようにするオプション サービスです。 HistoryContext は、Code First の DbContext であり、テーブルの名前や列のマッピング仕様などを変更するために、通常の fluent API を使用して構成できます。 EF によってすべてのプロバイダーに返されるこのサービスの既定の実装は、すべての既定のテーブルと列のマッピングがそのプロバイダーによってサポートされている場合、特定のデータベース サーバーに対して機能する可能性があります。 このような場合、プロバイダーではこのサービスの実装を提供する必要がありません。

IDbProviderFactoryResolver

これは、指定された DbConnection オブジェクトから正しい DbProviderFactory を取得するためのオプション サービスです。 EF によってすべてのプロバイダーに返されるこのサービスの既定の実装は、すべてのプロバイダーに対して機能するよう設計されています。 ただし、.NET 4 での実行時には、DbConnections の場合、パブリックに DbProviderFactory にアクセスできません。 そのため、EF ではいくつかのヒューリスティックを使用して登録されているプロバイダーを検索し、一致するものを見つけます。 一部のプロバイダーについては、これらのヒューリスティックが失敗することがあります。このような状況では、プロバイダーが新しい実装を提供する必要があります。

DbProviderServices の登録

使用する DbProviderServices 実装の登録は、アプリケーションの構成ファイル (app.config または web.config) で行うことも、コード ベースの構成を使用して行うこともできます。 どちらの場合も、登録では、キーとしてプロバイダーの "不変名" が使用されます。 これにより、複数のプロバイダーを登録して 1 つのアプリケーションで使用できます。 EF の登録に使用される不変名は、ADO.NET プロバイダーの登録や接続文字列で使用される不変名と同じです。 たとえば SQL Server の場合は、不変名 "System.Data.SqlClient" が使用されます。

構成ファイルの登録

使用する DbProviderServices 型は、アプリケーションの構成ファイルの entityFramework セクションにある、プロバイダー リストに、provider 要素として登録されます。 次に例を示します。

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

type の文字列は、使用する DbProviderServices 実装のアセンブリ修飾型名にする必要があります。

コード ベースの登録

EF6 以降では、コードを使用してプロバイダーを登録することもできます。 この方法では、アプリケーションの構成ファイルに変更を加えることなく EF プロバイダーを使用できます。 コード ベースの構成を使用するには、コード ベースの構成に関するドキュメントの説明に従って、アプリケーションで DbConfiguration クラスを作成する必要があります。 その後 DbConfiguration クラスのコンストラクターで、SetProviderServices を呼び出して EF プロバイダーを登録する必要があります。 次に例を示します。

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

追加のサービスの解決

プロバイダー型の概要」セクションで前述したように、DbProviderServices クラスを使用して追加のサービスを解決することもできます。 これは、IDbDependencyResolver は DbProviderServices によって実装されていて、登録された各 DbProviderServices 型は、"既定のリゾルバー" として追加されているためです。 IDbDpendencyResolver のメカニズムについては、「依存関係の解決」で詳細に説明されています。 ただし、プロバイダーで追加のサービスを解決するために、この仕様のすべての概念を理解する必要はありません。

プロバイダーで追加のサービスを解決する場合に最も一般的なのは、DbProviderServices クラスのコンストラクター内の各サービスに対して、DbProviderServices.AddDependencyResolver を呼び出す方法です。 たとえば SqlProviderServices (SQL Server 用の EF プロバイダー) には、初期化用に、これに似たコードが含まれています。

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

このコンストラクターでは、以下のヘルパー クラスを使用しています。

  • SingletonDependencyResolver: シングルトン サービスを解決する簡単な方法を提供します。つまり、GetService が呼び出されるたびに同じインスタンスを返すサービスです。 一時的なサービスは、多くの場合、オンデマンドで一時的なインスタンスを作成するために使用されるシングルトン ファクトリとして登録されます。
  • ExecutionStrategyResolver: IExecutionStrategy 実装を返す場合に固有のリゾルバー。

DbProviderServices.AddDependencyResolver を使用する代わりに、DbProviderServices.GetService をオーバーライドして、追加のサービスを直接解決することもできます。 このメソッドを呼び出すのは、EF で必要なサービスが、特定の型で、場合によっては特定のキーに対して定義されたものの場合です。 このメソッドでは、可能な場合はサービスを返す必要があります。そうでなければ、サービスを返すことをオプトアウトする null を返し、代わりに、別のクラスにその解決を許可する必要があります。 たとえば、既定の接続ファクトリを解決する場合、GetService 内のコードは次のようになります。

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

登録の順序

アプリケーションの構成ファイルに複数の DbProviderServices 実装が登録されていると、それらは列挙されている順序でセカンダリ リゾルバーとして追加されます。 リゾルバーは常にセカンダリ リゾルバー チェーンの一番上に追加されます。これは、リストの最後にあるプロバイダーが、他のリゾルバーの前に依存関係を解決する機会を得ることを意味します。 (これは、当初直感に反するように思えるかもしれませんが、リストから各プロバイダーを取り出し、既存のプロバイダーの上に積み重ねると考えれば、理にかなっています。)

ほとんどのプロバイダー サービスはプロバイダー固有であり、プロバイダーの不変名によってキー付けされるため、この順序は、通常は問題になりません。 ただし、プロバイダーの不変名やその他何らかのプロバイダー固有のキーによってキー付けされていないサービスの場合、この順序に基づいてサービスの解決が行われます。 たとえば、別の場所で明示的に異なる設定が行われていない場合、既定の接続ファクトリは、チェーン内の最上位のプロバイダーから取得されます。

追加の構成ファイルの登録

上で説明した追加のプロバイダー サービスの一部を、明示的に、アプリケーションの構成ファイルに直接登録できます。 これが行われると、DbProviderServices 実装の GetService メソッドによって返されるものではなく、構成ファイル内の登録が使用されます。

既定の接続ファクトリの登録

EF5 より、EntityFramework の NuGet パッケージでは、SQL Express 接続ファクトリまたは LocalDb 接続ファクトリのいずれかを構成ファイルに自動的に登録するようになりました。

次に例を示します。

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

type は、IDbConnectionFactory を実装する必要がある、既定の接続ファクトリのアセンブリ修飾型名です。

プロバイダーの NuGet パッケージでは、インストールの際にこの方法で既定の接続ファクトリを設定することをお勧めします。 下の「プロバイダー用の NuGet パッケージ」を参照してください。

その他の EF6 プロバイダーの変更点

空間プロバイダーの変更点

空間型をサポートするプロバイダーでは、DbSpatialDataReader から派生したクラスに、追加のメソッドをいくつか実装する必要があるようになりました。

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

また、既存のメソッドの新しい非同期バージョンも用意されています。これらは、同期メソッドに対する既定の実装のデリゲートとしてオーバーライドし、それによって非同期的に実行されないようにすることをお勧めしています。

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

Enumerable.Contains のネイティブ サポート

EF6 では、新しい式型の DbInExpression が導入されています。これは、LINQ クエリでの Enumerable.Contains の使用に関するパフォーマンス上の問題に対処するために追加されました。 DbProviderManifest クラスに、新しい仮想メソッド SupportsInExpression が用意されています。これは、新しい式型がプロバイダーによって処理されるかどうかを判定するために、EF によって呼び出されます。 既存のプロバイダー実装との互換性のために、このメソッドからは false が返されます。 この改善を利用するため、EF6 プロバイダーに、DbInExpression を処理し、SupportsInExpression をオーバーライドして true を返すコードを追加できます。 DbInExpression のインスタンスは、DbExpressionBuilder.In メソッドを呼び出すことで作成できます。 DbInExpression インスタンスは、通常、テーブル列を表している DbExpression と、一致をテストするための DbConstantExpression のリストで構成されます。

プロバイダー用の NuGet パッケージ

EF6 プロバイダーを使用できるようにする 1 つの方法は、それを NuGet パッケージとしてリリースすることです。 NuGet パッケージを使用すると以下の利点があります。

  • NuGet を使用してアプリケーションの構成ファイルにプロバイダーの登録を追加することは簡単です
  • 構成ファイルに追加の変更を加えて、規則によって作成される接続で登録済みのプロバイダーが使用されるように既定の接続ファクトリを設定できます
  • NuGet では、新しい EF パッケージのリリース後であっても EF6 プロバイダーが引き続き動作するように、バインディングのリダイレクトの追加が処理されます

この例として、オープン ソース コードベースに含まれている EntityFramework.SqlServerCompact パッケージがあります。 このパッケージは、EF プロバイダーの NuGet パッケージを作成する際の優れたテンプレートとなっています。

PowerShell コマンド

EntityFramework NuGet パッケージをインストールすると、2 つのコマンドが含まれる PowerShell モジュールが登録されます。これらは、プロバイダー パッケージ用に非常に役立ちます。

  • Add-EFProvider を使用すると、そのプロバイダーを表す新しいエンティティが、対象プロジェクトの構成ファイルに追加されて、登録済みプロバイダーのリストの最後に存在することが確認されます。
  • Add-EFDefaultConnectionFactory では、対象プロジェクトの構成ファイルに、defaultConnectionFactory の登録を追加または更新します。

これらのコマンドのどちらでも、構成ファイルへの entityFramework セクションの追加と、必要に応じたプロバイダー コレクションの追加が処理されます。

これらのコマンドは、install.ps1 NuGet スクリプトから呼び出すように設計されています。 たとえば、SQL Compact プロバイダー用の install.ps1 は、次のようになります。

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>

これらのコマンドの詳細については、パッケージ マネージャーのコンソール ウィンドウで get-help を使用すると取得できます。

ラッピング プロバイダー

ラッピング プロバイダーは、既存のプロバイダーを、プロファイルやトレースの機能など、その他の機能を使用して拡張するためにラップする、EF や ADO.NET のプロバイダーです。 ラッピング プロバイダーは通常の方法で登録できますが、多くの場合、プロバイダー関連サービスの解決を途中で引き継ぐことで実行時にラッピング プロバイダーを設定する方が便利です。 これを行うために、DbConfiguration クラスの静的イベント OnLockingConfiguration を利用できます。

OnLockingConfiguration を呼び出すのは、EF によってアプリ ドメインのすべての EF 構成がどこで取得されるかが特定された後の、それが使用のためにロックされる前です。 アプリの起動時 (EF が使用される前) に、アプリで、このイベントのイベント ハンドラーを登録する必要があります。 (Microsoft では、このハンドラーの、構成ファイルでの登録を追加でサポートすることを検討中ですが、まだサポートされていません。)イベント ハンドラーでは、次に、ラップする必要があるすべてのサービスに対して ReplaceService を呼び出す必要があります。

たとえば、IDbConnectionFactory と DbProviderService をラップするには、次のようなハンドラーを登録する必要があります。

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

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

解決済みで、サービスの解決に使用されたキーと一緒にここでラップする必要があるサービスは、ハンドラーに渡されます。 ハンドラーでは、その後、このサービスをラップして、返されたサービスを、ラップされたバージョンに置き換えることができます。

EF を使用した DbProviderFactory の解決

DbProviderFactory は、上の「プロバイダー型の概要」セクションで説明したように、EF で必要な基本的プロバイダー型の 1 つです。 既に触れたように、これは EF 型ではなく、登録は通常、EF 構成の一部ではなく、machine.config ファイルやアプリケーションの構成ファイルで行われる通常の ADO.NET プロバイダー登録です。

これにも関わらず、EF では、使用する DbProviderFactory を探すときに引き続き通常の依存関係解決メカニズムが使用されます。 既定のリゾルバーでは、構成ファイルでの通常の ADO.NET の登録が使用されるので、これは通常は透過的です。 ただし、通常の依存関係解決メカニズムが使用されているために、これは、通常の ADO.NET 登録が行われていなくても、IDbDependencyResolver を使用して DbProviderFactory を解決できることを意味します。

この方法で DbProviderFactory を解決すると、いくつかの影響があります。

  • コード ベースの構成を使用するアプリケーションでは、呼び出しを DbConfiguration クラスに追加して、適切な DbProviderFactory を登録できます。 これは特に、どのファイル ベースの構成も一切使用したくない (またはできない) アプリケーションにとって便利です。
  • このサービスは、上記の「ラッピング プロバイダー」セクションで説明したように、ReplaceService を使用してラップしたり置換したりすることができます
  • 理論的には、DbProviderServices の実装によって DbProviderFactory を解決できます。

これらのどれを実行する場合でも注意すべき重要な点は、これらの影響が及ぶのは、EF による DbProviderFactory の参照のみとなる点です。 EF 以外の他のコードでは、引き続き通常の方法で ADO.NET プロバイダーを登録することが求められ、登録が見つからなければ失敗する可能性があります。 こうした理由で、通常は、いつもの ADO.NET での方法で DbProviderFactory を登録するのが好ましい方法です。

EF を使用して DbProviderFactory を解決する場合は、IProviderInvariantName サービスと、IDbProviderFactoryResolver サービスも解決する必要があります。

IProviderInvariantName は、特定の型の DbProviderFactory について、プロバイダーの不変名を特定するために使用されるサービスです。 このサービスの既定の実装では、ADO.NET プロバイダー登録が使用されます。 つまり、EF によって DbProviderFactory が解決されるために、通常の方法では ADO.NET プロバイダーが登録されていない場合、このサービスの解決も必要になります。 このサービスのリゾルバーは、DbConfiguration.SetProviderFactory メソッドの使用時に自動的に追加されることに注意してください。

上の「プロバイダー型の概要」セクションで説明したように、IDbProviderFactoryResolver を使用して、指定された DbConnection オブジェクトから正しい DbProviderFactory を取得します。 このサービスの既定の実装では、.NET 4 で実行されている場合、ADO.NET プロバイダー登録が使用されます。 つまり、EF によって DbProviderFactory が解決されるために、通常の方法では ADO.NET プロバイダーが登録されていない場合、このサービスの解決も必要になります。