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 保持するプロパティの参照プロパティの メソッドを使用します。 (たとえば、 メソッドを PersonReference.Load 呼び出してエンティティの Person ナビゲーション プロパティを Department 読み込みます)。

    Image06

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

遅延読み込みは、デザイナーによって生成されたオブジェクト コンテキストの既定の動作です。 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 データ モデルを開き、デザイン画面をクリックし、プロパティ ウィンドウで [Lazy Loading Enabled]\(遅延読み込み有効\) プロパティを にFalse設定します。 データ モデルを保存して閉じます。

Image04

ビューステートの管理

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

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

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

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

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

<%@ 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"します。
  • コントロールから削除 ViewStateMode="Disabled" します Label

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

<%@ 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 クエリを初めて実行するときにコンパイルのすべての結果をキャッシュするコードを記述できます。

図として、 クラスの 2 つの Get メソッドに SchoolRepository 対してこれを行います。そのうちの 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();
        }
    }
}

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

次に、 SchoolRepository.cs で、リポジトリ クラスの既存 GetInstructorNames の メソッドと GetDepartmentsByAdministrator メソッドを変更して、コンパイルされたクエリを呼び出すようにします。

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

Departments.aspx ページを実行して、以前と同じように動作することを確認します。 GetInstructorNames管理者ドロップダウン リストを設定するために メソッドが呼び出されGetDepartmentsByAdministrator、[更新] をクリックするとメソッドが呼び出され、講師が複数の部門の管理者でないことを確認します。

Image03

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

データベースに送信されたクエリの確認

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

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

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必要があるのは、前の行の末尾にある メソッドが オブジェクトをIQueryable作成するためWhereだけです。メソッドがないと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の句を含む 1 つのSelectステートメントで必要なすべての情報を収集するクエリが複雑になり、効率的に動作できなくなる場合があります。 その場合は、一括読み込みから明示的な読み込みに切り替えて、クエリを簡略化できます。

たとえば、SchoolRepository.cs の メソッドのコードGetDepartmentsByNameを変更してみてください。 現在、そのメソッドには、 および Courses ナビゲーション プロパティのPersonメソッドを含む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

最初の ADO.NET 行をクリックして、前に表示した複雑なクエリに何が起こったかを確認します。

Image14

Departments からのクエリは、句のないJoin単純なSelectクエリになりましたが、その後に関連するコースと管理者を取得する個別のクエリが続きます。元のクエリによって返される各部門に対して 2 つのクエリのセットを使用します。

Note

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

ビューの事前生成

オブジェクトが 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 アプリケーションのパフォーマンス向上の概要が完了します。 詳細については、次のリソースを参照してください。

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