ASP.NET 4 Web アプリケーションでの Entity Framework 4.0 を使用したパフォーマンスの最大化

著者: Tom Dykstra

このチュートリアル シリーズは、Entity Framework 4.0 の概要チュートリアル シリーズで作成された Contoso University Web アプリケーションを基にして作られています。 前のチュートリアルを終えていない場合は、このチュートリアルの始めに、前のチュートリアルで作成するはずであったアプリケーションをダウンロードできます。 完全なチュートリアル シリーズで作成されるアプリケーションをダウンロードすることもできます。 チュートリアルに関する質問がある場合は、ASP.NET Entity Framework フォーラムに質問を投稿できます。

前のチュートリアルでは、コンカレンシーの競合を処理する方法を見ました。 このチュートリアルでは、Entity Framework を使う ASP.NET Web アプリケーションでパフォーマンスを向上させるためのオプションを示します。 パフォーマンスを最大化したり、パフォーマンスの問題を診断したりするための、いくつかの方法について説明します。

以下のセクションで説明する情報は、さまざまなシナリオで役に立つ可能性があります。

  • 関連データを効率よく読み込みます。
  • ビュー状態を管理します。

以下のセクションで説明する情報は、個々のクエリでパフォーマンスの問題が発生している場合に役に立つ可能性があります。

  • NoTracking マージ オプションを使います。
  • LINQ クエリをプリコンパイルします。
  • データベースに送信されるクエリ コマンドを調べます。

次のセクションで説明する情報は、非常に大きなデータ モデルを使うアプリケーションで役に立つ可能性があります。

  • ビューを事前に生成します。

Note

Web アプリケーションのパフォーマンスは、要求と応答のデータのサイズ、データベース クエリの速さ、サーバーがキューに登録できる要求の数とそれを処理する速さ、さらには使っているクライアント スクリプト ライブラリの効率など、さまざまな要因によって影響を受けます。 パフォーマンスが重要なアプリケーションの場合、またはテストや経験でアプリケーションのパフォーマンスが十分ではないことが示されている場合は、パフォーマンス チューニングに関する通常のプロトコルに従う必要があります。 測定を行ってパフォーマンスのボトルネックが発生している場所を特定してから、アプリケーションの全体的なパフォーマンスに最も大きな影響を与える領域に対処します。

このトピックでは、特に ASP.NET での Entity Framework のパフォーマンスを向上させられる可能性がある方法に主に焦点を当てます。 ここで提案することは、データ アクセスがアプリケーションのパフォーマンスのボトルネックの 1 つであると判断される場合に役に立ちます。 特に指定のない限り、ここで説明する方法は、一般に "ベスト プラクティス" とは見なさないでください。その多くは、例外的な状況でのみ、または極めて特殊な種類のパフォーマンス ボトルネックに対処する場合にのみ適しています。

チュートリアルを開始するには、Visual Studio を起動し、前のチュートリアルで使用していた Contoso University Web アプリケーションを開きます。

Entity Framework がエンティティのナビゲーション プロパティに関連データを読み込むには、次の複数の方法があります。

  • 遅延読み込み。 エンティティが最初に読み込まれるときに、関連データは取得されません。 ただし、ナビゲーション プロパティに初めてアクセスしようとすると、そのナビゲーション プロパティに必要なデータが自動的に取得されます。 これにより、エンティティ自体に対して 1 つと、エンティティの関連データを取得するたびに 1 つのように、複数のクエリがデータベースに送信されます。

    Image05

一括読み込み。 エンティティが読み取られるときに、関連データがエンティティと共に取得されます。 これは通常、必要なすべてのデータを取得する 1 つの結合クエリになります。 これらのチュートリアルで既に見たように、Include メソッドを使って一括読み込みを指定します。

Image07

  • 明示的読み込み。 これは遅延読み込みと似ていますが、関連データをコードで明示的に取得する点が異なります。コードの取得は、ナビゲーション プロパティにアクセスしても自動的には発生しません。 コレクションのナビゲーション プロパティの Load メソッドを使って手動で関連データを読み込むか、単一のオブジェクトを保持するプロパティに対する参照プロパティの Load メソッドを使います。 (たとえば、Department エンティティの Person ナビゲーション プロパティを読み込むには、PersonReference.Load メソッドを呼び出します)。

    Image06

プロパティ値はすぐには取得されないため、遅延読み込み (lazy loading) と明示的読み込み (explicit loading) はどちらも遅延読み込み (deferred loading) と呼ばれます。

遅延読み込みは、デザイナーによって生成されるオブジェクト コンテキストの既定の動作です。 オブジェクト コンテキスト クラスが定義されている SchoolModel.Designer.cs ファイルを開くと、3 つのコンストラクター メソッドがあり、それぞれに次のステートメントが含まれています。

this.ContextOptions.LazyLoadingEnabled = true;

一般に、取得したすべてのエンティティの関連データが必要な場合は、データベースに送信された 1 つのクエリの方が、取得した各エンティティに対する分離したクエリよりも効率的なため、一括読み込みを使用すると、より最適なパフォーマンスが得られます。 一方、エンティティのナビゲーション プロパティにアクセスする必要がある頻度が低い場合、またはアクセスする必要があるのが少数のエンティティのセットである場合は、一括読み込みでは必要以上に多くのデータが取得されるため、遅延読み込みまたは明示的読み込みの方が効率的な場合があります。

Web アプリケーションでは、関連データの必要性に影響を与えるユーザー アクションがブラウザーで発生し、ページをレンダリングしたオブジェクト コンテキストへの接続がないため、いずれにしても遅延読み込みにはあまり価値がない場合があります。 一方、コントロールをデータバインドする場合は、通常、必要なデータがわかっているので、一般に、各シナリオでの適切さに基づいて、一括読み込みまたは遅延読み込みを選ぶのが最善です。

さらに、データバインドされたコントロールでは、オブジェクト コンテキストが破棄された後でエンティティ オブジェクトを使う場合があります。 その場合、ナビゲーション プロパティを遅延読み込みしようとすると失敗します。 そのことがはっきりわかるエラー メッセージが表示されます: "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."

EntityDataSource コントロールでは、遅延読み込みが既定で無効にされます。 現在のチュートリアルで使っている ObjectDataSource コントロールの場合 (または、ページ コードからオブジェクト コンテキストにアクセスする場合)、遅延読み込みを既定で無効にする方法がいくつかあります。 オブジェクト コンテキストをインスタンス化するときに無効にできます。 たとえば、SchoolRepository クラスのコンストラクター メソッドに次の行を追加できます。

context.ContextOptions.LazyLoadingEnabled = false;

Contoso University アプリケーションでは、オブジェクト コンテキストの遅延読み込みを自動的に無効にして、コンテキストがインスタンス化されるたびにこのプロパティを設定する必要がないようにします。

SchoolModel.edmx データ モデルを開き、デザイン サーフェイスをクリックしてから、プロパティ ウィンドウで [遅延読み込みが有効です] プロパティを False に設定します。 データ モデルを保存して閉じます。

Image04

ビュー状態の管理

ASP.NET Web ページで更新機能を提供するには、ページがレンダリングされるときにエンティティの元のプロパティ値を保存する必要があります。 ポストバック処理中に、コントロールはエンティティの元の状態を作成し直し、変更を適用して SaveChanges メソッドを呼び出す前に、エンティティの Attach メソッドを呼び出すことができます。 既定では、ASP.NET Web Forms のデータ コントロールはビュー状態を使って元の値を保存します。 ただし、ビュー状態は非表示フィールドに格納され、ブラウザーとの間で送受信されるページのサイズが大きく増えることがあるため、パフォーマンスに影響する可能性があります。

ビュー状態を管理する手法や、セッション状態などの代替手段は、Entity Framework に固有のものではないため、このチュートリアルではこのトピックについて詳しく説明しません。 詳細については、チュートリアルの最後にあるリンクを参照してください。

ただし、ASP.NET のバージョン 4 で提供されている、ビュー状態の新しい操作方法である ViewStateMode プロパティについては、Web Forms アプリケーションのすべての ASP.NET 開発者が認識している必要があります。 ページまたはコントロール レベルで設定できるこの新しいプロパティを使うと、ビュー状態をページでは既定で無効にし、必要なコントロールに対してのみ有効にすることができます。

パフォーマンスが重要なアプリケーションでは、ページ レベルではビュー状態を常に無効にし、必要なコントロールについてのみ有効にするのがよい方法です。 Contoso University ページのビュー状態のサイズは、この方法ではそれほど小さくなりませんが、その動作を確認するため、Instructors.aspx ページでそれを行います。 そのページには、ビュー状態が無効になっている Label コントロールなど、多くのコントロールが含まれています。 このページのコントロールのいずれでも、実際には、ビュー状態を有効にする必要はありません。 (GridView コントロールの DataKeyNames プロパティでは、ポストバック間で維持する必要がある状態が指定されていますが、これらの値はコントロール ステートに保持され、ViewStateMode プロパティの影響を受けません)。

Page ディレクティブと Label コントロールのマークアップは、現在、次の例のようになっています。

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label> 
    ...

次の変更を行います。

  • Page ディレクティブに ViewStateMode="Disabled" を追加します。
  • Label コントロールから ViewStateMode="Disabled" を削除します。

マークアップは次の例のようになります。

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" 
    ViewStateMode="Disabled" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false"></asp:Label> 
    ...

これで、すべてのコントロールでビュー状態が無効になります。 後でビュー状態を使う必要があるコントロールを追加する場合、行う必要があるのは、そのコントロールに ViewStateMode="Enabled" 属性を含めることだけです。

NoTracking マージ オプションの使用

オブジェクト コンテキストは、データベースの行を取得し、それらを表すエンティティ オブジェクトを作成する場合、既定では、オブジェクト状態マネージャーを使ってそれらのエンティティ オブジェクトの追跡も行います。 この追跡データはキャッシュとして機能し、エンティティを更新するときに使われます。 通常、Web アプリケーションのオブジェクト コンテキスト インスタンスの有効期間は短く、多くの場合、クエリが返すデータを追跡する必要はありません。これは、それらを読み取るオブジェクト コンテキストは、読み取ったエンティティが再度使用または更新される前に、破棄されてしまうためです。

Entity Framework では、"マージ オプション" を設定して、オブジェクト コンテキストがエンティティ オブジェクトを追跡するかどうかを指定できます。 マージ オプションは、個々のクエリまたはエンティティ セットに対して設定できます。 それをエンティティ セットに対して設定した場合は、そのエンティティ セットに対して作成されるすべてのクエリに既定のマージ オプションを設定することを意味します。

Contoso University アプリケーションの場合、リポジトリからアクセスするどのエンティティ セットについても追跡する必要はないため、リポジトリ クラスでオブジェクト コンテキストをインスタンス化するとき、それらのエンティティ セットのマージ オプションを NoTracking に設定できます。 (このチュートリアルでは、マージ オプションを設定しても、アプリケーションのパフォーマンスに大きな影響はないことに注意してください。NoTracking オプションで、はっきりわかるほどパフォーマンスが向上する可能性があるのは、特定の高データ ボリューム シナリオのみです。)

DAL フォルダーの SchoolRepository.cs ファイルを開き、リポジトリがアクセスするエンティティ セットのマージ オプションを設定するコンストラクター メソッドを追加します。

public SchoolRepository()
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    context.InstructorNames.MergeOption = MergeOption.NoTracking;
    context.OfficeAssignments.MergeOption = MergeOption.NoTracking;
}

LINQ クエリのプリコンパイル

Entity Framework が特定の ObjectContext インスタンスの有効期間内に Entity SQL クエリを初めて実行するときは、クエリのコンパイルに時間がかかります。 コンパイルの結果はキャッシュされるため、後続のクエリの実行ははるかに速くなります。 LINQ クエリも同様のパターンに従いますが、クエリのコンパイルに必要な作業の一部は、クエリが実行されるたびに行われます。 つまり、LINQ クエリの場合、既定では、コンパイルの結果がすべてキャッシュされるわけではありません。

オブジェクト コンテキストの有効期間中に繰り返し実行する LINQ クエリがある場合は、その LINQ クエリが初めて実行されるときに、コンパイルのすべての結果をキャッシュするようにコードを記述できます。

例として、SchoolRepository クラスの 2 つの Get メソッドについてこれを行います。そのうちの 1 つはパラメーターを受け取らず (GetInstructorNames メソッド)、もう 1 つはパラメーターを必要とします (GetDepartmentsByAdministrator メソッド)。 これらのメソッドは、LINQ クエリではないので、実際にはコンパイルする必要はありません。

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return new ObjectQuery<Department>("SELECT VALUE d FROM Departments as d", context, MergeOption.NoTracking).Include("Person").Where(d => d.Administrator == administrator).ToList();
}

ただし、コンパイル済みのクエリを試すことができるように、これらが次の LINQ クエリとして記述されているものとして続けます。

public IEnumerable<InstructorName> GetInstructorNames()
{
    return (from i in context.InstructorNames orderby i.FullName select i).ToList();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    return (from d in context.Departments where d.Administrator == administrator select d).ToList();
}

続ける前に、これらのメソッドのコードを上で示すように変更し、アプリケーションを実行して動作することを確認します。 ただし、次の手順では、プリコンパイル済みバージョンをすぐに作成します。

DAL フォルダーにクラス ファイルを作成し、SchoolEntities.cs という名前を付けて、既存のコードを次のコードに置き換えます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects;

namespace ContosoUniversity.DAL
{
    public partial class SchoolEntities
    {
        private static readonly Func<SchoolEntities, IQueryable<InstructorName>> compiledInstructorNamesQuery =
            CompiledQuery.Compile((SchoolEntities context) => from i in context.InstructorNames orderby i.FullName select i);

        public IEnumerable<InstructorName> CompiledInstructorNamesQuery()
        {
            return compiledInstructorNamesQuery(this).ToList();
        }

        private static readonly Func<SchoolEntities, Int32, IQueryable<Department>> compiledDepartmentsByAdministratorQuery =
            CompiledQuery.Compile((SchoolEntities context, Int32 administrator) => from d in context.Departments.Include("Person") where d.Administrator == administrator select d);

        public IEnumerable<Department> CompiledDepartmentsByAdministratorQuery(Int32 administrator)
        {
            return compiledDepartmentsByAdministratorQuery(this, administrator).ToList();
        }
    }
}

このコードは、自動的に生成されるオブジェクト コンテキスト クラスを拡張する部分クラスを作成します。 部分クラスには、CompiledQuery クラスの Compile メソッドを使用する 2 つのコンパイル済み LINQ クエリが含まれます。 また、クエリの呼び出しに使用できるメソッドも作成されます。 このファイルを保存して閉じます。

次に、SchoolRepository.cs で、リポジトリ クラスの既存の GetInstructorNamesGetDepartmentsByAdministrator メソッドを、コンパイル済みクエリを呼び出すように変更します。

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.CompiledInstructorNamesQuery();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return context.CompiledDepartmentsByAdministratorQuery(administrator);
}

Departments.aspx ページを実行して、前と同じように動作することを確認します。 GetInstructorNames メソッドは管理者のドロップダウン リストを設定するために呼び出され、GetDepartmentsByAdministrator メソッドは、複数の学科の管理者になっているインストラクターがいないことを確認するために、[Update] がクリックされると呼び出されます。

Image03

Contoso University アプリケーションでクエリをプリコンパイルしたのは、その方法を確認するためだけであり、それによってパフォーマンスが大幅に向上するためではありません。 LINQ クエリをプリコンパイルすると、コードがかなり複雑になるため、アプリケーションで実際にパフォーマンスのボトルネックになっているクエリに対してのみ行ってください。

データベースに送信されたクエリの調査

パフォーマンスの問題を調べているとき、Entity Framework がデータベースに送信している SQL コマンドが正確にわかると役に立つ場合があります。 IQueryable オブジェクトを使っている場合、これを行う 1 つの方法は、ToTraceString メソッドを使うことです。

SchoolRepository.csGetDepartmentsByName メソッドのコードを、次の例と一致するように変更します。

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Include("Person").Include("Courses").Where(d => d.Name.Contains(nameSearchString));
    string commandText = ((ObjectQuery)departments).ToTraceString();
    return departments.ToList();
}

departments 変数を ObjectQuery 型にキャストする必要があるのは、前の行の最後にある Where メソッドが IQueryable オブジェクトを作成するためだけです。Where メソッドがない場合、キャストは必要ありません。

return 行にブレークポイントを設定してから、デバッガーで Departments.aspx ページを実行します。 ブレークポイントにヒットしたら、[ ローカル] ウィンドウで commandText 変数を調べ、テキスト ビジュアライザー ([値] 列の虫眼鏡) を使って、[テキスト ビジュアライザー] ウィンドウにその値を表示します。 このコードの結果として作成される SQL コマンド全体を確認できます。

Image08

代わりに、Visual Studio Ultimate の IntelliTrace 機能を使うと、コードの変更やブレークポイントの設定の必要なしに、Entity Framework によって生成される SQL コマンドを表示できます。

Note

次の手順は、Visual Studio Ultimate を使っている場合にのみ実行できます。

GetDepartmentsByName メソッドを元のコードに戻してから、デバッガーで Departments.aspx ページを実行します。

Visual Studio で、[デバッグ] メニュー、[IntelliTrace][IntelliTrace イベント] の順に選びます。

Image11

[IntelliTrace] ウィンドウで、[すべて中断] をクリックします。

Image12

[IntelliTrace] ウィンドウに、最近のイベントの一覧が表示されます。

Image09

ADO.NET の行をクリックします。 次のように展開してコマンド テキストが表示されます。

Image10

[ローカル] ウィンドウからクリップボードに、コマンド テキスト文字列全体をコピーできます。

単純な School データベースより多くのテーブル、リレーションシップ、列を含むデータベースを使用しているとします。 複数の Join 句を含む単一の Select ステートメントのクエリを使って、必要なすべての情報を収集しようとすると、複雑になりすぎて効率的に作業できない可能性があります。 その場合は、一括読み込みから明示的読み込みに切り替えてクエリを簡単にできます。

たとえば、SchoolRepository.csGetDepartmentsByName メソッドのコードを変更してみてください。 現在、そのメソッドには、PersonCourses ナビゲーション プロパティに対する Include メソッドを含むオブジェクト クエリがあります。 return ステートメントを、次の例で示すような明示的読み込みを実行するコードに置き換えます。

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Where(d => d.Name.Contains(nameSearchString)).ToList();
    foreach (Department d in departments)
    {
        d.Courses.Load();
        d.PersonReference.Load();
    }
    return departments;
}

デバッガーで Departments.aspx ページを実行し、前と同じように [IntelliTrace] ウィンドウをもう一度確認します。 以前は 1 つのクエリであったところが、クエリの長いシーケンスになっています。

Image13

1 番目の ADO.NET 行をクリックし、前に見た複雑なクエリがどうなっているか確認します。

Image14

Departments からのクエリは、Join 句を持たない単純な Select クエリになっていますが、その後に関連するコースと管理者を取得する個別のクエリがあり、元のクエリによって返される学科ごとに 2 つのクエリのセットが使われています。

Note

遅延読み込みを有効のままにしていると、ここで見られるような、同じクエリが何度も繰り返されるパターンが、遅延読み込みの結果として発生する可能性があります。 通常、避けたいパターンは、プライマリ テーブルのすべての行の関連データを遅延読み込みすることです。 単一の結合クエリでは複雑すぎて効率が悪いことが確認されているのでない限り、通常は、一括読み込みを使うようにプライマリ クエリを変更すると、このような場合のパフォーマンスを向上させることができます。

ビューの事前生成

新しいアプリケーション ドメインで ObjectContext オブジェクトが初めて作成されるとき、Entity Framework はデータベースへのアクセスに使われるクラスのセットを生成します。 これらのクラスは "ビュー" と呼ばれ、非常に大きなデータ モデルがある場合、これらのビューの生成によって、新しいアプリケーション ドメインが初期化された後のページに対する最初の要求への Web サイトの応答が、遅れる可能性があります。 実行時ではなくコンパイル時にビューを作成すると、この最初の要求の遅延を減らすことができます。

Note

アプリケーションに非常に大きなデータ モデルがない場合、または大きなデータ モデルがあっても、IIS がリサイクルされた後の最初のページ要求のみに影響するパフォーマンスの問題が気にならない場合は、このセクションをスキップしてかまいません。 ビューはアプリケーション ドメインにキャッシュされるため、ObjectContext オブジェクトをインスタンス化するたびにビューが作成されることはありません。 そのため、IIS でアプリケーションを頻繁にリサイクルするのでない限り、ビューの事前生成によってメリットがあるページ要求はごくわずかです。

EdmGen.exe コマンドライン ツールを使うか、"テキスト テンプレート変換ツールキット" (T4) テンプレートを使って、ビューを事前に生成できます。 このチュートリアルでは、T4 テンプレートを使います。

DAL フォルダーで、テキスト テンプレート テンプレート ([インストール済みのテンプレート] の一覧の [全般] ノードの下にあります) を使ってファイルを追加し、SchoolModel.Views.tt という名前を付けます。 ファイルの既存のコードを次のコードに置き換えます。

<#
/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.

THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/
#>

<#
    //
    // TITLE: T4 template to generate views for an EDMX file in a C# project
    //
    // DESCRIPTION:
    // This is a T4 template to generate views in C# for an EDMX file in C# projects.
    // The generated views are automatically compiled into the project's output assembly.
    //
    // This template follows a simple file naming convention to determine the EDMX file to process:
    // - It assumes that [edmx-file-name].Views.tt will process and generate views for [edmx-file-name].EDMX
    // - The views are generated in the code behind file [edmx-file-name].Views.cs
    //
    // USAGE:
    // Do the following to generate views for an EDMX file (e.g. Model1.edmx) in a C# project
    // 1. In Solution Explorer, right-click the project node and choose "Add...Existing...Item" from the context menu
    // 2. Browse to and choose this .tt file to include it in the project 
    // 3. Ensure this .tt file is in the same directory as the EDMX file to process 
    // 4. In Solution Explorer, rename this .tt file to the form [edmx-file-name].Views.tt (e.g. Model1.Views.tt)
    // 5. In Solution Explorer, right-click Model1.Views.tt and choose "Run Custom Tool" to generate the views
    // 6. The views are generated in the code behind file Model1.Views.cs
    //
    // TIPS:
    // If you have multiple EDMX files in your project then make as many copies of this .tt file and rename appropriately
    // to pair each with each EDMX file.
    //
    // To generate views for all EDMX files in the solution, click the "Transform All Templates" button in the Solution Explorer toolbar
    // (its the rightmost button in the toolbar) 
    //
#>
<#
    //
    // T4 template code follows
    //
#>
<#@ template language="C#" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ output extension=".cs" #>
<# 
    // Find EDMX file to process: Model1.Views.tt generates views for Model1.EDMX
    string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".views", "") + ".edmx";
    string edmxFilePath = Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), edmxFileName);
    if (File.Exists(edmxFilePath))
    {
        // Call helper class to generate pre-compiled views and write to output
        this.WriteLine(GenerateViews(edmxFilePath));
    }
    else
    {
        this.Error(String.Format("No views were generated. Cannot find file {0}. Ensure the project has an EDMX file and the file name of the .tt file is of the form [edmx-file-name].Views.tt", edmxFilePath));
    }
    
    // All done!
#>

<#+
    private String GenerateViews(string edmxFilePath)
    {
        MetadataLoader loader = new MetadataLoader(this);
        MetadataWorkspace workspace;
        if(!loader.TryLoadAllMetadata(edmxFilePath, out workspace))
        {
            this.Error("Error in the metadata");
            return String.Empty;
        }
            
        String generatedViews = String.Empty;
        try
        {
            using (StreamWriter writer = new StreamWriter(new MemoryStream()))
            {
                StorageMappingItemCollection mappingItems = (StorageMappingItemCollection)workspace.GetItemCollection(DataSpace.CSSpace);

                // Initialize the view generator to generate views in C#
                EntityViewGenerator viewGenerator = new EntityViewGenerator();
                viewGenerator.LanguageOption = LanguageOption.GenerateCSharpCode;
                IList<EdmSchemaError> errors = viewGenerator.GenerateViews(mappingItems, writer);

                foreach (EdmSchemaError e in errors)
                {
                    // log error
                    this.Error(e.Message);
                }

                MemoryStream memStream = writer.BaseStream as MemoryStream;
                generatedViews = Encoding.UTF8.GetString(memStream.ToArray());
            }
        }
        catch (Exception ex)
        {
            // log error
            this.Error(ex.ToString());
        }

        return generatedViews;
    }
#>

このコードは、テンプレートと同じフォルダーにあり、テンプレート ファイルと同じ名前を持つ .edmx ファイルの、ビューを生成します。 たとえば、テンプレート ファイルの名前が SchoolModel.Views.tt の場合、SchoolModel.edmx という名前のデータ モデル ファイルが検索されます。

ファイルを保存してから、ソリューション エクスプローラーでそのファイルを右クリックして、[カスタム ツールの実行] を選びます。

Image02

Visual Studio は、テンプレートに基づく SchoolModel.Views.cs という名前のビューを作成するコード ファイルを生成します。 (テンプレート ファイルを保存するとすぐ、[カスタム ツールの実行] を選ぶ前であっても、コード ファイルが生成されることに気付くかもしれません)。

Image01

これで、アプリケーションを実行し、前と同じように動作することを確認できます。

事前に生成されるビューについて詳しくは、次のリソースを参照してください。

これで、Entity Framework を使用する ASP.NET Web アプリケーションでのパフォーマンス向上の概要は終わりです。 詳細については、次のリソースを参照してください。

次のチュートリアルでは、Entity Framework バージョン 4 で新しく行われたいくつかの重要な機能強化について確認します。