2019 年 8 月
Volume 34 Number 8
[データ ポイント]
.NET Core 3.0 を使用したクロスプラットフォームの EF6
Entity Framework Core の提供開始から数年経ちますが、EF6 を使用している運用アプリはいまだに大量にあります。EF Core にどのようなイノベーションが組み込まれていても、EF6 ロジックを変更する計画がなく、EF Core の新機能や向上したパフォーマンスを利用する必要がなければ、安定して動作している運用コードを EF Core に移植する理由はありません。EF6 は、オープン ソースという性質上、いまだに Microsoft やコミュニティによって維持されているだけでなく、微調整が加えられています。しかし、多くの開発者やチームは、作成した EF6 機能に手を加えたくないものの、クロスプラットフォーム機能や多くのイノベーションなど、.NET Core のさまざまな利点を活用するために、開発したソフトウェアを .NET から.NET Core に移植したいと考えています。
これまでも、EF6 ロジックを ASP.NET Web API にカプセル化して、.NET Core アプリケーションからアクセスすることは可能でした。ただし、そのソリューションでは、.NET Core 3.0 に移行することになるため、ASP.NET Core API シリーズの機能や Windows デスクトップ アプリに対するイノベーションを活用することはできません。さいわいなことに、EF 6.3 と .NET Core 3.0 の次期リリースでは、それを両立させることができます。 EF6.3 は、引き続き .NET Framework 上で実行されるだけなく、.NET Core 3.0 でも動作します。これが可能なのは、EF6.3 は、.NET 4.0 と .NET 4.5 上での実行に加えて、.NET Standard 2.1 をターゲットにクロスコンパイルされるためです。
この記事では、EF6.3 と ASP.NET Core 3.0 の最新のプレビュー バージョンを使用して、クロスプラットフォーム シナリオで EF6.3 を試してみます。具体的には、EF6.3 を使用して、新しい ASP.NET Core 3.0 API を作成します。MacBook のmacOS 上でVisual Studio Code を使用します。その後、それを Linux ベースの Docker コンテナーにデプロイします。これまで EF6 では、これらのどのタスクも不可能でした。EF6.3 は、今までどおり、Visual Studio 2017 v15.3 以降で .NET Framework アプリに使用できます。一方、.NET Core 3.0 アプリについては、.NET Core 3.0 をサポートする Visual Studio 2019 のプレビュー バージョンが必要になります。
EF6.3 プレビューには .NET Core 3.0 に関していくつかの制限事項がありますが、リリースまでに解消する見込みです。現在の制限事項は次のとおりです。
- .NET Core 3.0 は、Visual Studio の EF Designer ではまだ使用できません。
- 移行コマンドは、.NET Core 3.0 プロジェクトではまだ機能しません。
- 執筆時点で機能しているプロバイダーは SQL Server だけです。
発表に関するブログ記事に付いているディスカッションが役に立つかもしれませんが (bit.ly/2F9xtDt)、こうした問題の多くはリリースが近付くと解決されることを覚えておいてください。
こうした制限事項がありますが、本当に関心があるのは概念実証、つまり、Windows 以外のマシン上で EF6.3 を試すことです。そのため、最も簡単なモデルを使用して、非常にシンプルなプロジェクトを構築します。それで、MacBook 上で動作することを十分確認できます。macOS 上で作業します。現在利用できるデータベース プロバイダーは SQL Server のみです。そういうわけで、Docker コンテナーで SQL Server for Linux を使用して、データを保持します。
Base API プロジェクトの準備
.NET Core 3.0 のインストールと ASP.NET Core API の作成はこれまでどおりで変わりありません。まだ完了していない人のために、その手順を簡単に説明します。
最初に、ご使用のマシンに .NET Core 3.0 を確実にインストールします。この記事を執筆しているのは 2019 年 6 月初旬なので、最新バージョンは 2019 年 6 月 12 日にリリースされた Preview 6 です。.NET Core 3.0 のインストール ページは bit.ly/2KoSOxh です。SDK パッケージをインストールする必要があります。これには、SDK だけでなく、.NET Core と ASP.NET Core のランタイムも含まれています。インストールが完了すると、dotnet --version コマンドで最新バージョンが一覧表示され、dotnet --list-sdks でご使用のシステム上にあるすべてのバージョンが表示されます。
次に、このプロジェクト用の新しいフォルダーを作成し ("coreapi" という名前を付けました)、コマンド ラインでそのフォルダーに確実に移動します。その後、CLI コマンド「dotnet new webapi」を入力します。そうすると、既定の言語として C# を使用し、ご利用のマシン上にインストールされている .NET Core の最新バージョンを既定で使用する新しい ASP.NET Core API プロジェクトがフォルダー内に作成されます。
MacBook 上で作業しているので、自分の開発環境に Visual Studio Code を使用することで、クロスプラットフォーム サポートをすぐに体験できます。ショートカットとして、コマンド ラインでプロジェクト フォルダーからコード「.」を入力して、VS Code を起動し、そのフォルダーを開くことができます。単純に VS Code を起動し、プロジェクト フォルダーを開くこともできます。
既定の ValuesController など、プロジェクトの重要なアセットはテンプレートによって作成されています。図 1 に示すように、プロジェクト ファイルは .NET Core 3.0 をターゲットにしています。このバージョンの .NET Core では、csproj 内でなんらかの ASP.NET Core パッケージを明示的に参照する必要がなくなりました。そのため、ItemGroup が空になっています。
図 1 coreapi プロジェクトとその csproj ファイルの内容
プロジェクトへの EF6.3 の追加
ItemGroup セクション内に次のパッケージ参照を含めることで、csproj ファイルに EF6.3 を直接追加できます。
<PackageReference Include="EntityFramework" Version="6.3.0-preview6-19304-03" />
これでは、この記事の執筆時点で利用可能な最新のプレビュー バージョンを参照していることに注意してください。最新バージョンは、NuGet の Entity Framework に関するページ (bit.ly/2RgTo0l) で確認してください。この記事を読んでいる頃には、EF6.3 が既に全面的にリリースされているかもしれません。
格納するデータが必要なので、Human という小さなクラスを作成しました。
public class Human {
public int HumanId { get; set; }
public string Name { get; set; }
}
ダウンロード サンプルには名前空間と using ステートメントが含まれていますが、ここに示したコードには含まれていない点に注意してください。
データを保持するために、HumanContext という DbContext クラスを作成しました。
public class HumanContext : DbContext {
public HumanContext (string connectionString) : base (connectionString)
{
Database.SetInitializer<HumanContext> (new HumanInitializer ());
}
public DbSet<Human> Humans { get; set; }
protected override void OnModelCreating (DbModelBuilder modelBuilder
{
modelBuilder.Entity<Human> ().ToTable ("Humans");
}
}
このコンストラクターには、接続文字列を渡す必要があります。
EF で Human という単語を複数形の Humen に変換する処理を完全に失敗してしまうことがないように、マッピングを使って、テーブル名を Humans に強制的に設定しました。さらに、コンストラクター内で、データベース初期化子をカスタム初期化子 HumanInitializer に設定します。これにより、モデルが変更された場合に、ロマンチックな人物がテスト データベースにシードされます。EF6 のレッスンの内容を忘れている場合のために、この初期化子ではデータベースがまだ存在しなければその作成も行われることをお伝えしておきます。HumanInitializer クラスは次のようになります。
public class HumanInitializer : DropCreateDatabaseIfModelChanges<HumanContext>
{
protected override void Seed (HumanContext context)
{
context.Humans.AddRange (new Human[] {
new Human { Name = "Juliette" },
new Human { Name = "Romeo" }
});
}
}
ASP.NET Core と EF6 の結び付け
ASP.NET Core で EF6.3 を使用する場合に見落としてしまう EF Core の利点がいくつかあります。1 つは、依存関係の挿入の統合です。EF Core は、AddDbContext 拡張メソッドを使用して、自身を ASP.NET Core サービスに関連付けることができます。これにより、ASP.NET Core から、コントローラー クラスなど、DbContext のオブジェクト インスタンスを必要とするクラスにそのインスタンスをオンデマンドで挿入できます。ただし、AddDbContext メソッドは EF Core でなければ利用できません。さらに、Microsoft.EntityFrameworkCore.SqlServer などの EF Core データベース プロバイダーには、AddDbContext メソッドにプロバイダーの詳細を追加できる拡張メソッドが用意されています。たとえば、SqlServer には、接続文字列などのオプションを指定できる UseSqlServer メソッドがあります。
これらのメソッドは EF6.3 を使用している場合は利用できませんが、HumanContext を ASP.NET Core の依存関係の挿入サービスに接続し、プロバイダーと接続文字列について知らせることはできます。別のコードを使用する必要があるだけです。そのコードは、EF Core コードと同じ場所、つまり、API の startup クラスの ConfigureServices メソッド内に配置します。
IServiceCollection Add メソッド (および AddScoped などのバリエーション) を使用すると、任意のクラスをそのサービスに追加できます。そうすると、実行中にそのオブジェクトが必要になったときに処理方法の指示を渡すことができます。
このコードでは、HumanContext オブジェクトが必要な場所をチェックし、応答として、新しい HumanContext をインスタンス化し、appsettings.json 内にある接続文字列を渡すよう伝えます。
services.AddScoped<HumanContext>
(serviceProvider => new HumanContext (Configuration["Connection"]));
このようにして、HumanContext の依存関係の挿入を処理します。
UseSqlServer メソッドがないのに、どうやってデータベース プロバイダーと接続文字列を指定するのでしょうか。EF6.3 Preview 6 の時点では、プロバイダーが指定されていない場合、EF は System.Data.SqlClient を使用します。つまり、ここでは SQL Server を使用するので、コードを追加する必要がないのです。この場合、SQL Server が既定のプロバイダーとなります。
この接続の最後の部分では、接続文字列を appsettings.json に挿入して、Configuration["Connection"] メソッドで読み取れるようにします。
"Connection":"Server=localhost,1601;Database=Humans;User Id=sa;Password=P@ssword1"
MacBook 上で作業するので、Docker コンテナーでデータベースとして SQL Server for Linux を使用すると先ほど言ったことを思い出してください。コンテナーを実行したときに、自分のマシン上でデータベース サーバーを公開するポートとして 1601 を指定しました。コンテナーを実行するために使用した docker コマンドを次に示します。
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@ssword1'
-p 1601:1433 -d --name SQLforEF6 mcr.microsoft.com/mssql/server:2017-latest
このコマンドは 1 行で記述する必要があることに注意してください。コンテナー内での SQL Server の使用の詳細については、2017 年 7 月の私の記事「On-the-Fly SQL Servers with Docker (Docker を使用したオンザフライの SQL Servers)」(msdn.com/magazine/mt784660) をご覧ください。その記事との違いの 1 つは、ここでは Microsoft コンテナー レジストリ (docker run コマンドのターゲットである、画像の URL の "mcr") 内の Microsoft の Docker コンテナーの新しいホームを指している点です。
EF6 を使用するコントローラーの追加
テンプレートによって作成されたコントローラーは、自分の API が動作していることをテストする良い方法ですが、ビルドして文字列を返すだけです。データベース操作をテストするために、図 2 に示す新しい HumanController クラスを追加します。この概念実証のために本当に必要なのは、1 つの HttpGet メソッドでデータを取得することだけです。データの挿入は、HumanInitializer の Seed メソッドによって処理されます。
図 2 HumanController クラス
[Route ("api/[controller]")]
[ApiController]
public class HumanController : ControllerBase
{
HumanContext _context;
public HumanController (HumanContext context) {
_context = context;
}
[HttpGet]
public ActionResult<IEnumerable<Human>> Get ()
{
return _context.Humans.ToList ();
}
}
アプリケーション インスタンス内で EF が最初にデータベース操作を試行するのは、HumanContext コンストラクター内に定義した初期化がトリガーされたときです。そのため、このメソッドを実行するまで、データベースの作成とシードは実行されません。これは簡単なデモなので、このアプリケーションを複数のサーバーに展開する可能性については考慮していません。複数のサーバーに展開すると、同時に初期化とシードのコードを実行しようとして、競合が発生することがあります。そのため、アプリケーションでデータベースの作成とシードを実行できるようにする場合は、この副作用に必ず留意してください。
API の実行
ついにすべての準備が整ったので、API を実行またはデバッグできます。私はいつも最初にコマンド ラインから dotnet run を使用して実行します。実行したら、まず localhost:5000/api/values で values コントローラーを参照して、API 自体が動作していることを確認します。これにより、"value1" と "value2" がブラウザーに出力されます。次に、localhost:5000/api/human でデータベースに依存する API にアクセスします。すべてうまくいけば、一番最初の実行ではそれほど時間がかからずに、API によって Humans データベースが作成され、Humans テーブルの作成とシードが実行されます。完了すると、図 3 に示すように、ID と 2 人の人物の名前が出力されます。
図 3 coreapi の Get メソッドの結果
コンテナー化されたデータベースの確認
これによって、データベースが作成され、シードされたことは十分に証明されています。しかし、私は、特にコンテナー化されたサーバーの場合、必ずデータベースも確認しないと気が済まない質です。
docker ps コマンドを呼び出して、実行中のコンテナーを一覧表示すると、コンテナーが実際に実行されていることが証明されます。
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND
4bfc01930095 mcr.microsoft.com/mssql/server:2017-latest "/opt/mssql/bin/sqls…"
CREATED STATUS PORTS NAMES
23 hours ago Up 13 hours 0.0.0.0:1601->1433/tcp SQLforEF6
VS Code Docker 拡張機能の Docker Explorer は、自分の SQLforEF6 コンテナーが実行されていることを確認するもう 1 つの方法です。
Azure Data Studio を使用すると、そのコンテナーの SQL Server インスタンスに接続して、データベースとそのデータを確認できます (図 4 を参照)。このツールの詳細については、Azure Data Studio に関する私の以前の記事 (msdn.com/magazine/mt832858) をご覧ください。
図 4 Azure Data Studio での新しいデータベースの確認
Linux の Docker への EF6.3 依存 API のデプロイ
Windows に依存するアプリを Windows コンテナーに配置することはできますが、Linux コンテナーの方が作業はずっと簡単です。EF6.3 ではクロスプラットフォームを実行できるようになったため、API の Linux ベースのイメージを作成します。
イメージを作成するだけでなく、このイメージに基づいてコンテナーを実行し、コンテナー内の SQL Server と通信できるようにする docker-compose ファイルを作成します。このマガジンの 2019年 4 月、5 月、6 月の 3 部シリーズの私の記事では、このすべてのしくみを掘り下げています。そのため、ここでは重要なポイントだけをお伝えします。また、すべてのコードはサンプル ダウンロードで確認できます。
ここでも、VS Code Docker 拡張機能がこの作業の大部分に役立ちます。F1 キーを押した後、「Docker」と入力すると、この拡張機能が提供するさまざまなコマンドが表示されます。[Docker:Add Docker Files to Workspace] を選択します。 メッセージが表示されたら、アプリケーション プラットフォームとして ASP.NET Core を選択します。残りの指示に従って、OS として Linux を選択し、既定のポート 80 を選択します。
ただし、.NET Core 3.0 Preview 6 をターゲットにしているため、テンプレートによって生成された Dockerfile を更新する必要があります。Microsoft コンテナー レジストリ (MCR) から直接 Preview 6 のバージョンをプルするように FROM イメージ ターゲットを変更する必要がありました。Dockerfile の最初の 4 つの行を次のように変更します。
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6 AS build
次に、この新しいイメージのビルド、コンテナー内での実行、API が通信できる別の SQL Server コンテナーの実行を調整するために、docker-compose ファイルを追加しました。私の以前の記事をまだ読んでいない場合、この点が重要になります。これにより、データベース コンテナーを見つける方法を API コンテナーが認識します。
図 5 には、このプロジェクトに追加した docker-compose.yml ファイルを示しています。
図 5 API とデータベース コンテナーの両方を実行する docker-compose.yml ファイル
version: '3.4'
services:
coreapi:
image: ${DOCKER_REGISTRY-}api
build:
context: .
dockerfile: Dockerfile
ports:
- 80:80
db:
image: mcr.microsoft.com/mssql/server
environment:
SA_PASSWORD: "P@ssword1"
ACCEPT_EULA: "Y"
ports:
- "1601:1433"
最後に変更するのは、API の appsettings.json ファイル内の接続文字列です。現在は、localhost 1601 を指しています。しかし、新しいコンテナー内の ASP.NET API の場合、その localhost 上にサーバーがありません。そこで、docker-compose ファイル内のサービス名 (db) と一致するようにサーバー名を変更すると、Docker により、API コンテナーがデータベース コンテナーを確実に見つけられるようになります。
appsettings.json 内に記述する新しい接続を次に示します。
"Connection":"Server=db;Database=Humans;User Id=sa;Password=P@ssword1"
これだけです。これで docker-compose を使用してコンテナーを実行できます。ドキュメント エクスプローラーで docker-compose.yml ファイルを右クリックすると、Docker 拡張機能により、オプションの 1 つに [Compose Up] が表示されます。それを選択し、完了するまで待ちます。SQL Server がシステム データベースのセットアップを完了するまでに、30 秒ほど余計にかかる場合があります。これで、localhost:80/api/huma で API を参照できます。Humans データベースを作成してシードするのに、また少し時間がかかります。SQL Server のこの処理には少々時間がかかりますが、完了したら、API を参照して、図 4 と同じ出力を確認できます。そうしたら、yml ファイルをもう一度右クリックし、[Compose Down] を選択して、新しいコンテナーを削除します。
Windows なしの EF6.3
これまで EF6.3 を実際に試したことがありませんでしたが、Windows 以外の環境でこの概念実証全体を行うことができたことに驚き、感心しています。個人的には、新しいプロジェクトを開始するのに、EF6.3 を使いません。新しい作業にはすべて EF Core を使用します。特にクロスプラットフォームの面で、非常に多くの利点があるからです。しかし、EF6 を問題なく活用している運用コードがあり、依存するアプリで .NET Core の機能を利用できるようにする場合は、それがクロスプラットフォーム機能であっても、その他の優れた機能であっても、EF6 のままそれを実現できるようになります。
Julie Lermanは、バーモント ヒルズ在住の Microsoft Regional Director、Microsoft MVP、Docker キャプテン、ソフトウェア チームの指導者です。世界中のユーザー グループやカンファレンスで、データ アクセスなどのトピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2010 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。彼女の Twitter (@julielerman、英語) をフォローし、bit.ly/PS-Julie (英語) で彼女の Pluralsight コースをご覧ください。
この記事のレビューに協力してくれたマイクロソフト技術スタッフの Diego Vega (Diego.Vega@microsoft.com)、Brice Lambson <(bricelam@microsoft.com)>