Julie Lerman
私はこれまで、Entity Framework (EF) を使用している既存のソフトウェアのリファクタリングについて、多数の顧客をサポートしてきました。EF コードの場合、リファクタリングにはさまざまな意味があり、更新も必要になることがよくあります。今月のコラムでは、既存の EF コードを見直す方法や、EF を使用するアプリケーションを見直す方法を取り上げます。紹介する各アプローチでは、顧客の運用環境アプリケーションを扱ってきた私の経験に基づいて、ガイドをいくつか示します。このようなガイドは、十分に準備を整えて問題を回避するうえで役立つことと思います。
説明する変更のシナリオは、以下のとおりです。
- 新しいバージョンの Entity Framework に更新する
- 大きなエンティティ データ モデルを細分化する<</li>
- ObjectContext API を DbContext API に置き換える
今月は、1 つ目と 2 つ目のシナリオを大まかに説明します。次回のコラムでは、ガイドや具体例と共に、最後のシナリオについて詳しく説明します。
これらの変更に着手する前に、ぜひ実施していただきたいアドバイスがあります。それは "1 つずつ行う" ことです。以前に私は、EF4、大きなモデル、および ObjectContext API を使用する旧式プロジェクトに携わったたことがあり、このとき先ほどの 3 点を同時に変更しようと試みましたが、ひどく苦労しました。苦労という表現では足りないと言ってもよいでしょう。この場合にお勧めの手順として、最初は EF のバージョンを更新するだけで他には何も変更せず、すべてが正常に動作し続けることを確認します。次に、モデル内の領域のうち、新しい小さなモデルに抽出できる部分を特定します。ただし最初のうちは、新しいモデルの対象を ObjectContext API のままにします。すべてが正常な動作に復帰したら DbContext API への移行を開始しますが、移行するのは先ほど抽出した小さなモデルだけです。他の部分も移行すると、アプリケーション全体の破損箇所が非常に多くなって、さまざまなバグを探し回るという荒っぽくて支離滅裂な作業が必要になるおそれがあります。小さなモデルを 1 つずつ移行すれば、手直しが必要な破損コードが少なくなります。また、次の小さなモデルを移行する際に役立つ知識やパターンも学べます。
新しいバージョンの Entity Framework に更新する
EF チームが下位互換性を重視しているおかげで、バージョンの移行にかかる手間は最小限で済みます。このコラムでは、EF6 への更新 (メジャー バージョンの EF6 だけでなく、EF6.02 や EF6.1 などへのマイナー アップデートも含みます) に重点を置いて説明します。
EF4、EF4.1、または EF5 から EF6 に移行する際の最大の問題 (個人的には、それほど深刻ではないと思っています) は、一部の名前空間が変更されることが原因です。移行元の API は Microsoft .NET Framework に存在し続けているので、EF6 と重複すると問題が発生します。したがって、EF6 の API では、競合を回避するためにこのようなクラスが System.Data とは別の名前空間に存在しています。たとえば、.NET ベースの EF API には、System.Data.Objects、System.Data.Common、System.Data.Mapping、System.Data.Query などで始まる多数の名前空間があります。System.Data.EntityException や System.Data.EntityState など、System.Data 直下に存在しているクラスや列挙体もいくつかあります。このように System.Data に直接結び付いているクラスや名前空間のほとんどは、System.Data.Entity.Core という新しい名前空間ルートに移行されました。System.Data.Entity.EntityState に移行された EntityState など、System.Data.Entity に移行されたクラスもいくつかあります。たとえば、Mapping 名前空間は System.Data.Entity.Core.Mapping に存在するようになり、Objects 名前空間は System.Data.Entity.Core.Objects に存在するようになりました。System.Data.Entity.Core に移行されなかった特定の例外については、データ アクセス デベロッパー センターのドキュメント「EF6 へのアップグレードを行う」(bit.ly/OtrKvA、英語) で 4 つ目のトピックを参照してください。
既存のアプリケーションを EF6 に更新する際、私は "… 型または名前空間が見つかりません" というエラーをコンパイラに表示して変更点を確認しています。その後、ソリューション全体で名前空間を検索して置き換え、名前空間を修正します。
ここでは、例として拙書『Programming Entity Framework』(O'Reilly Media、2010 年) 第 2 版のに掲載している小さなサンプル ソリューションを出発点にしました。このソリューションの作成には、EF4、EDMX モデル、コードで生成した POCO エンティティ クラス、および ObjectContext API を使用しています。Code First と DbContext API については、作成当時は存在していませんでした。 作業の開始前には、アプリケーションが今でも正常に動作することを確認しました (Visual Studio 2013 でデバッグしました)。
ここでは EF4 から EF6 への大規模な移行を取り上げますが、EF5 から EF6 に移行する場合も同じ方法で名前空間を修正する必要があります。というのも、名前空間の変更は EF5 と EF6 の間で行われているためです。EF4 から EF6 に直接移行する場合は、もう少し追加の作業が必要になります。さらに、今回の移行元ソリューションでは、移行先に直接対応するテクノロジがない T4 テンプレートを使用しています。
EF4 から EF6 に更新する今回の手順
NuGet パッケージ マネージャーを使用して Entity Framework を入手するのに慣れている方は、EF が .NET Framework の一部にすぎなかった時代、つまり EF のすべての DLL が Windows グローバル アセンブリ キャッシュ (GAC) に含まれていた時代を思い出してください。EF6 に更新する前に、今回は移行元ソリューションの各プロジェクトで、System.Data.Entity (バージョン 4.0.0.0) への参照を手動で削除しました。また、BIN フォルダーに格納するよう設定していた可能性がある元の DLL をすべて削除するために、ソリューションのクリーンを行いました (ソリューション エクスプローラーでソリューションを右クリックし、[ソリューションのクリーン] をクリックしました)。ただし後になって、EF6 の NuGet パッケージ インストーラーによって以前の参照が削除されるために、この作業は不必要だったことが判明しました。
続いて、NuGet を使用して関連プロジェクトに EF6 をインストールし、ソリューションをリビルドしました。実際のソリューションにおけるプロジェクトの依存関係に応じて、名前空間の問題が検出されるタイミングは異なります。今回のソリューションでは、最初は名前空間のエラーが 1 つだけ検出されました。このエラーを修正してソリューションをリビルドしたところ、さらに多くのエラーが検出されましたが、そのうち名前空間以外の問題は 1 つだけでした。この問題については、コンパイラの便利なメッセージから、ソリューションで利用していた使用頻度の低い属性 (EdmFunction) が別の名前に変更され (そのために "廃止" とマークされ)、DbFunction という属性に置換されていたことがわかりました。
名前空間の修正とリビルドを数回繰り返すと (小さなソリューションだったのでわずか数分で完了しました)、アプリケーションを正常にビルドして実行する (データの表示、編集、保存を行う) ことができるようになりました。
ObjectContext と POCO を生成する T4 テンプレートを修正する
注意が必要な、実行する可能性がある作業はもう 1 つあります。移行元ソリューションでは、EDMX (EF Designer で設計して管理するエンティティ データ モデル) を使用していました。また、このソリューションは EF4 を使用する Visual Studio 2010 で作成したので、以前のコード生成テンプレートに依存しており、このテンプレートから生成した ObjectContext でデータの永続化とキャッシュ全体を管理していました。また、このテンプレートからは、モデルのエンティティから POCO クラス (Entity Framework に依存しないクラス) も生成していました。モデルに変更を加える場合はクラスとコンテキストを生成し直す必要がありますが、以前のテンプレート (コンテキストを生成したテンプレート) では、新しい名前空間が認識されません。DbContext テンプレートと ObjectContext (および、EntityObject) テンプレートは存在していますが (図 1 参照)、ObjectContext と POCO を生成する元のテンプレートに相当するテンプレートはありません。しかも、使用したテンプレートにはカスタマイズを行っていました。そのため、アプリケーションと連携しない新しいテンプレートを選択する代わりに、自作のソリューションに保存されていた Context.tt テンプレートに対して、以下のような 2 つのちょっとした変更を加えました。
- 42 行目で、"using System.Data.Objects;" を "using System.Data.Entity.Core.Objects;" に変更する。
- 43 行目で、"using System.Data.EntityClient;" を "using System.Data.Entity.Core.EntityClient;" に変更する。
図 1 DbContext と POCO を生成するテンプレート、または ObjectContex と非 POCO を生成するテンプレートは存在する
これで、モデルからクラスを生成し直すたびに、ObjectContext クラスに適切な名前空間が設定され、POCO で生成したカスタム クラスがアプリケーションで機能し続けるようになりました。前述したデータ アクセス デベロッパー センターのドキュメントには、サポートされているテンプレートの使い方が説明されています。
コードを変更しなくても得られるメリット
EF6 を使用するようアプリケーションを更新する作業が非常に簡単なことはわかりましたが、考慮すべき非常に重要な点があります。アプリケーションでは最新バージョンの Entity Framework を使用するようになりましたが、得られたメリットは、Entity Framework の基盤となる強化点 (特に、EF5 と EF6 の大幅なパフォーマンス向上) だけです。このパフォーマンス向上は主に EF5 で実現されているので、他の新機能を活用せずに EF5 から EF6 に移行する場合は、パフォーマンスはあまり向上しません。EF6 のその他の新機能については、2013 年 12 月号の記事「Entity Framework 6: 上級者向けエディション」(bit.ly/1qJgwlf) を参照してください。機能強化の多くは、DbContext API や Code First の関連機能です。EF6 と DbContext の両方に更新する予定の方は、まずは簡単に EF6 にアップグレードすることをお勧めします。その後、すべてが正常に機能することを確認してから、DbContext API への移行を開始してください。さらに複雑な変更方法もありますが、詳細については次回のコラムで説明します。
このように最小限とも言える変更を行うだけで、コードベースで最新の API を利用できるようになります。
大きなモデルを細分化する
モデルの作成に EF Designer を使用した場合も、Code First ワークフローを使用した場合も (msdn.microsoft.com/ja-jp/magazine/hh148150.aspxで「Entity Framework 手法の解明: モデル作成ワークフロー」を参照してください)、エンティティが多いモデルでは、設計時や実行時に問題が発生します。個人的な経験から言うと、大きなモデルは扱いにくく、保守も困難です。EF Designer で多数 (数百個) のエンティティを扱う場合は、EF Designer でモデルを開いて表示するのに時間がかかるだけでなく、モデルを表示しながら移動するのも困難です。ありがたいことに、この問題に役立つすばらしい機能が Visual Studio 2012 で EF Designer に追加されました。詳細については、「Entity Framework Designer が Visual Studio 2012 で評価される」( bit.ly/1kV4vZ8、英語) を参照してください。
それでも、モデルを小さくすることはいつでもお勧めです。私のコラム「DDD 境界コンテキストで EF モデルを縮小する」(bit.ly/1isIoGE、英語) では、モデルを小さくすることのメリットや、そのモデルをアプリケーションで使用するための手法をいくつか紹介しています。ただし、既に大きなモデルが存在する場合、細分化は困難です。一部の顧客は巨大なデータベースを基にリバース エンジニアリングを行ったモデルを扱っていましたが、エンティティ数が 700 ~ 1,000 個にまで達しました。EF Designer で EDMX モデルを縮小する場合も Code First を使用する場合も、細分化が難しい作業であることは同じです。
大きなモデルを小さなモデルに細分化して保守を容易にしたり、実行時のパフォーマンスを向上したりする場合に、役に立つ指針をいくつか紹介しましょう。
一度にモデル全体のリファクタリングは行いません。抽出する小さなモデルごとに、参照が変更されて場合によってはリレーションシップ コードにも影響が及ぶため、それぞれ追加でコード リファクタリングを行う必要があります。
したがって、まずはモデル内でほぼ独立しているセクションを特定します。最初から重複を考慮する必要はありません。たとえば、製品の製造販売を行う企業のシステムに取り組んでいるとしましょう。ソフトウェアには、販売員の個人データ、連絡先情報、販売地域などを管理するための機能が含まれている場合があります。また、ソフトウェアの別の部分では、販売地域の定義に基づいて顧客の注文を作成する際に、それらの販売員を参照する場合があります。さらに、販売手数料を追跡する部分もあります。このように 3 つの独立した部分のうち、一度に 1 つのシナリオ (販売員一覧の管理など) に取り組みます。
小さなモデルに移行する手順は、以下のとおりです。
- 対象のシナリオに関連しているエンティティを特定します (図 2 参照)。
- まったく新しいプロジェクトを作成します。
- 作成したプロジェクトで、関連しているエンティティが認識される新しいモデルを (EF Designer または Code First を使用して) 定義します。
- 製品の管理に関連しているアプリケーション コードを特定します。
- 作成した新しいコンテキストを使用するように、元のコンテキストを (クエリ、変更の保存、または別の機能のために) 使用した関連コードを更新します。
- 目的の機能が動作するまで既存のコードをリファクタリングします。自動テストを導入すると、リファクタリング作業の効率が向上します。
この作業で留意していただきたいその他のアドバイスは、以下のとおりです。
- これらのエンティティは、大きなモデルから削除しません。削除すると、さまざまなコードが破損します。この時点では、放置して無視してください。
- アプリケーションが新しい小さなモデルと連携するまでに必要だったリファクタリングの内容について、記録を残します。
図 2 他のエンティティにほとんど影響を与えることなく個別のモデルに抽出できる SalesPerson と Territory Maintenance
続けて別のモデルを大きなモデルから切り離すと、適用できるパターンを習得できます。
新しい小さなモデルを一度に 1 つずつソリューションに追加して、そのモデルを使用するようコードを変更することを繰り返すと、作業がいっそう快適になります。大きなモデルでは、同じエンティティが、SalesPerson の管理作業とは無関係なリレーションシップと結び付くおそれがあります。たとえば、SalesPerson から Order へのリレーションシップがあるとすれば、モデルから SalesPerson を完全に削除するのは望ましくないでしょう。簡単な方法としては、あるモデルに、Order の作成時に参照用に使用する縮小された読み取り専用の SalesPerson 型を配置してから、別のモデルに、管理に使用する完全で編集可能な SalesPerson 型 (Order が認識されない型) を配置します。これらの作業が完了すると、どちらのエンティティからも引き続き同じデータベースを参照できます。Code First と移行を使用する場合は、EF モデルの縮小について説明している前述の記事を参照して、複数の重複するモデルがある場合にデータベースを共有する方法を確認してください。
最終的には、小さなモデルが多数誕生し、大きなモデルへの参照がいくらか残ります。これで、大きなモデルで使用されなくなったエンティティを簡単に特定して、安全に削除できるようになります。
忍耐こそ美徳
最も重要な点は、このような更新とリファクタリングに少しずつ取り組み、変更を加えるたびに、必ずテストやアプリケーションを機能する状態に戻してから次の手順に進むことです。新しいバージョンの Entity Framework への更新は、独立した作業項目と見なします。その更新作業も 2 つに分けることができます。作業の前半では新しい API を取得し、後半では、コードが機能し続けることを確認してから、新機能を活用するようコードを変更します。このように少しずつ進んでいく手法は、大きなモデルを細分化する場合、特に ObjectContext から DbContext への更新を予定している場合にも当てはまります。小さなモデルを抽出し、この新しくて小さなモデルでも関連ロジックが機能するようにリファクタリングします。機能することを確認したら、ObjectContext との関係を解消します。このようにすると、最初は多くのコードが機能しなくなります。しかし少なくとも、この段階で機能しなくなるコードはコードベースの小さな領域に隔離されているので、一度にリファクタリングするコードが少なくて済みます。
次回のコラムでは、ObjectContext コードを移行して DbContext API を使用するという、さらに困難ながらも十分に達成可能な目標について掘り下げます。
Julie Lerman は、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog(英語) で、『Programming Entity Framework』(O'Reilly Media、2010 年)、および同書の Code First 版 (O'Reilly Media、2011 年) と DbContext 版 (O'Reilly Media、2012 年) の著者でもあります。Twitter (twitter.com/julielerman、英語) で彼女をフォローし、juliel.me/PS-Videos(英語) で Pluralsight のコースをご覧ください。
この記事のレビューに協力してくれたマイクロソフト技術スタッフの Rowan Miller に心より感謝いたします。