アプリケーションの起動時にデータをキャッシュする (C#)

作成者: Scott Mitchell

PDF のダウンロード

どのような Web アプリケーションにも、頻繁に使用されるデータと頻繁に使用されないデータがあります。 キャッシュと呼ばれる手法を使用して、頻繁に使用されるデータを事前に読み込むことで、ASP.NET アプリケーションのパフォーマンスを向上させることができます。 このチュートリアルでは、プロアクティブな読み込みの 1 つの手法である、アプリケーションの起動時にキャッシュにデータを読み込む手法を説明します。

はじめに

前の 2 つのチュートリアルでは、プレゼンテーションおよびキャッシュ レイヤーのデータのキャッシュについて説明しました。 「ObjectDataSource でデータをキャッシュする」では、ObjectDataSource のキャッシュ機能を使用してプレゼンテーション レイヤーにデータをキャッシュする方法について説明しました。 「アーキテクチャでデータをキャッシュする」では、新しい個別のキャッシュ レイヤーでキャッシュを行う方法について説明しました。 どちらのチュートリアルでも、データ キャッシュの操作にリアクティブな読み込みを使用しました。 リアクティブな読み込みの場合、データが要求されるたびに、システムはまずキャッシュ内にデータが存在するかどうかを確認します。 存在しない場合、データベースなどの元のソースからデータを取得し、これをキャッシュに格納します。 リアクティブな読み込みのの主な利点は、実装の容易さです。 その欠点の 1 つは、パフォーマンスが要求ごとで不均一になることです。 前のチュートリアルのキャッシュ レイヤーを使用して製品情報を表示するページについて考えてみます。 このページが初めてアクセスされた場合、または、メモリ制約や指定された有効期限に達したためにキャッシュされたデータが削除された後に初めてアクセスされた場合、データベースからデータを取得する必要があります。 そのため、これらのユーザー要求は、キャッシュで処理できるユーザー要求よりも時間がかかります。

プロアクティブな読み込みは代替のキャッシュ管理戦略であり、キャッシュされるデータが必要になる前にそれを読み込むことで、要求ごとのパフォーマンスの不均一を除去できます。 通常、プロアクティブな読み込みでは、基になるデータが更新されたかを定期的にチェックするプロセス、または、基になるデータが更新されたタイミングで通知が送信されるプロセスが使用されます。 その後にこのプロセスがキャッシュを更新して、最新の状態が保たれるようにします。 プロアクティブな読み込みは、低速なデータベース接続や Web サービスなど、速度が顕著に低いデータ ソースから基になるデータが取得される場合に特に便利です。 ただし、このプロアクティブな読み込みの手法の場合、変更を確認してキャッシュを更新するプロセスを作成、管理、デプロイする必要があるため、実装がより困難になります。

今回のチュートリアルで説明する、もう 1 つの種類のプロアクティブな読み込みでは、アプリケーションの起動時にデータをキャッシュに読み込みます。 この方法は、データベース検索テーブルのレコードなどの静的データをキャッシュする場合に特に便利です。

Note

プロアクティブな読み込みとリアクティブな読み込みの違い、および、長所、短所、実装の推奨事項の一覧については、「.NET Framework アプリケーションのキャッシュ アーキテクチャ ガイド」の「キャッシュのコンテンツの管理」セクションを参照してください。

手順 1: アプリケーションの起動時にキャッシュするデータを決定する

以前の 2 回のチュートリアルで説明したリアクティブな読み込みを使用するキャッシュの例は、定期的に変更される可能性があり、生成に著しく時間がかかるわけではないデータに対してうまく機能します。 ただ、キャッシュされたデータが変更されることが決してない場合、リアクティブな読み込みで使用される有効期限は不要になります。 同様に、キャッシュされるデータの生成に著しく時間がかかる場合で、ユーザーの要求の結果キャッシュが空であると判定された場合、そのユーザーは、基になるデータが取得されるまで長時間待機しなければならなくなります。 静的データ、および生成に著しく時間のかかるデータについては、アプリケーションの起動時にキャッシュすることを検討します。

データベースには動的で頻繁に変化する値が多数含まれますが、その一方で、ほとんどのデータベースにはかなりの量の静的データも含まれています。 たとえば、事実上すべてのデータ モデルには、変化することがない選択肢のセットに対応した特定の値を含む列が 1 つ以上含まれています。 Patients データベース テーブルであれば、English、Spanish、French、Russian、Japanese などの一連の値を持つ PrimaryLanguage 列があるでしょう。 多くの場合、これらの種類の列は、ルックアップ テーブルを使用して実装されます。 通常は、English や French の文字列を Patients テーブルに格納するのではなく、一意識別子と文字列の説明の 2 つの列を持つ 2 番目のテーブルが作成され、使用可能なそれぞれの値に対応するレコードが作成されます。 Patients テーブルの PrimaryLanguage 列には、ルックアップ テーブル内の対応する一意識別子が格納されます。 図 1 にて、患者 John Doe の主言語は英語であり、一方で Ed Johnson の場合はロシア語です。

The Languages Table is a Lookup Table Used by the Patients Table

図 1: Languages テーブルは、Patients テーブルによって使用されるルックアップ テーブルです

新しい患者を編集または作成するためのユーザー インターフェイスには、Languages テーブルのレコードにより設定される、選択可能な言語のドロップダウン リストが含まれることになります。 キャッシュを使用しない場合、システムは、このインターフェイスがアクセスされるたびに Languages テーブルに対してクエリを実行する必要があります。 ルックアップ テーブの値が変更されることは、仮にあるとしてもきわめて稀であるため、これは無駄であり不要です。

前のチュートリアルで説明したものと同じリアクティブな読み込みの手法を使用して、Languages データをキャッシュできます。 ただ、リアクティブな読み込みの場合は時間ベースの有効期限が使用されますが、これは静的なルックアップ テーブルのデータには必要ありません。 リアクティブな読み込みを使用してキャッシュを行うことは、キャッシュをまったく行わないよりも良いです。ただ、最善の方法は、アプリケーションの起動時にルックアップ テーブルのデータをキャッシュにプロアクティブに読み込むことです。

このチュートリアルでは、ルックアップ テーブルのデータやその他の静的な情報をキャッシュする方法について説明します。

手順 2: データをキャッシュするためのさまざまな方法を確認する

ASP.NET アプリケーションでは、さまざまな方法を使用して、プログラムで情報をキャッシュできます。 データ キャッシュを使用する方法については、前のチュートリアルですでに確認しました。 これとは別に、静的メンバーまたはアプリケーション状態を使用して、オブジェクトをプログラムでキャッシュすることもできます。

クラスを操作する場合、通常は、クラスを最初にインスタンス化してからそのメンバーにアクセスする必要があります。 たとえば、ビジネス ロジック レイヤーのいずれかのクラスのメソッドを呼び出すには、まずそのクラスのインスタンスを作成する必要があります。

ProductsBLL productsAPI = new ProductsBLL();
productsAPI.SomeMethod();
productsAPI.SomeProperty = "Hello, World!";

SomeMethod を呼び出したり SomeProperty を操作したりする前に、まず、new キーワードを使用してクラスのインスタンスを作成する必要があります。 SomeMethodSomeProperty は、特定のインスタンスに関連付けられます。 これらのメンバーの有効期間は、関連するオブジェクトの有効期間に関連付けられます。 一方で静的メンバーは、そのクラスのすべてのインスタンスで共有される変数、プロパティ、メソッドであり、その有効期間は結果的にクラスと同じになります。 静的メンバーはキーワード static で示されます。

静的メンバーの他に、アプリケーション状態を使用してデータをキャッシュすることもできます。 各 ASP.NET アプリケーションは、アプリケーションのすべてのユーザーとページで共有される名前/値のコレクションを保持します。 このコレクションには、HttpContext クラスApplication プロパティを使用してアクセスでき、次のように ASP.NET ページの分離コード クラスから使用できます。

Application["key"] = value;
object value = Application["key"];

データ キャッシュにはデータをキャッシュするための機能豊富な API が用意されており、時間および依存関係ベースの有効期限、キャッシュ項目の優先度などのメカニズムを利用できます。 静的メンバーとアプリケーション状態の場合、そのような機能をページ開発者が手動で追加する必要があります。 ただ、アプリケーションの起動時に、アプリケーションの有効期間にわたり使用されるデータをキャッシュする場合、データ キャッシュの利点は意味をなさなくなります。 このチュートリアルでは、静的データをキャッシュするための 3 つの手法をすべて使用するコードについて説明します。

手順 3: Suppliers テーブル データをキャッシュする

これまでに実装した Northwind データベース テーブルには、従来のルックアップ テーブルは含まれていません。 DAL に実装されている 4 つの DataTable はすべて、値が非静的であるモデル テーブルです。 今回のチュートリアルでは、時間をかけて新しい DataTable を DAL に追加し、新しいクラスとメソッドを BLL に追加することはせずに、単に Suppliers テーブルのデータは静的であるということにします。 そのため、このデータをアプリケーションの起動時にキャッシュできます。

まず、StaticCache.cs フォルダーに CL という名前の新しいクラスを作成します。

Create the StaticCache.cs Class in the CL Folder

図 2: CL フォルダーに StaticCache.cs クラスを作成する

起動時にデータを適切なキャッシュ ストアに読み込むメソッドと、このキャッシュからデータを返すメソッドを追加する必要があります。

[System.ComponentModel.DataObject]
public class StaticCache
{
    private static Northwind.SuppliersDataTable suppliers = null;
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using a static member variable
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        suppliers = suppliersBLL.GetSuppliers();
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return suppliers;
    }
}

上記のコードでは、静的メンバー変数 suppliers を使用して、LoadStaticCache() メソッドから呼び出される SuppliersBLL クラスの GetSuppliers() メソッドからの結果を保持します。 LoadStaticCache() メソッドは、アプリケーションの起動時に呼び出されることを意図しています。 アプリケーションの起動時にこのデータを読み込めば、サプライヤー データを操作する必要のあるページが StaticCache クラスの GetSuppliers() メソッドを呼び出せるようになります。 そのため、サプライヤーを取得するためのデータベースの呼び出しは、アプリケーションの起動時に一度だけ発生します。

キャッシュ ストアとして静的メンバー変数を使用する代わりに、アプリケーション状態またはデータ キャッシュを使用することもできます。 次のコードは、アプリケーション状態を使用するように修正されたクラスを示しています。

[System.ComponentModel.DataObject]
public class StaticCache
{
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using application state
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        HttpContext.Current.Application["key"] = suppliersBLL.GetSuppliers();
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return HttpContext.Current.Application["key"] as Northwind.SuppliersDataTable;
    }
}

LoadStaticCache() では、サプライヤー情報はアプリケーション変数 key に格納されます。 これは、GetSuppliers() から適切な型 (Northwind.SuppliersDataTable) として返されます。 アプリケーション状態は、Application["key"] を使用して ASP.NET ページの分離コード クラスでアクセスできますが、このアーキテクチャでは、現在の HttpContext を取得するために HttpContext.Current.Application["key"] を使用する必要があります。

同様に、次のコードに示すように、データ キャッシュをキャッシュ ストアとして使用できます。

[System.ComponentModel.DataObject]
public class StaticCache
{
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using the data cache
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        HttpRuntime.Cache.Insert(
          /* key */                "key", 
          /* value */              suppliers, 
          /* dependencies */       null, 
          /* absoluteExpiration */ Cache.NoAbsoluteExpiration, 
          /* slidingExpiration */  Cache.NoSlidingExpiration, 
          /* priority */           CacheItemPriority.NotRemovable, 
          /* onRemoveCallback */   null);
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return HttpRuntime.Cache["key"] as Northwind.SuppliersDataTable;
    }
}

時間ベースの有効期限なしで項目をデータ キャッシュに追加するには、System.Web.Caching.Cache.NoAbsoluteExpiration および System.Web.Caching.Cache.NoSlidingExpiration 値を入力パラメーターとして使用します。 キャッシュ項目の優先度を設定できるようにするために、データ キャッシュの Insert メソッドのこの特定のオーバーロードが選択されました。 優先度は、使用可能なメモリが不足しているときに清掃するキャッシュ内の項目を決定するために使用されます。 ここでは、優先度 NotRemovable を使用して、キャッシュ項目が清掃されないようにします。

Note

このチュートリアルのダウンロードでは、静的メンバー変数の手法を使用して StaticCache クラスを実装します。 アプリケーション状態とデータ キャッシュの手法のコードは、クラス ファイルのコメント内で確認できます。

手順 4: アプリケーションの起動時にコードを実行する

Web アプリケーションの初回の起動時にコードを実行するには、Global.asax という名前の特別なファイルを作成する必要があります。 このファイルには、アプリケーション、セッション、および要求レベルのイベントのイベント ハンドラーを含めることができ、ここで、アプリケーションの起動時に実行されるコードを追加できます。

Visual Studio のソリューション エクスプローラーで Web サイト プロジェクト名を右クリックし、[新しい項目の追加] を選択して、Global.asax ファイルを Web アプリケーションのルート ディレクトリに追加します。 [新しい項目の追加] ダイアログ ボックスで、[グローバル アプリケーション クラス] の項目の種類を選択し、[追加] ボタンをクリックします。

Note

プロジェクトに Global.asax ファイルがすでにある場合、[グローバル アプリケーション クラス] の項目の種類は [新しい項目の追加] ダイアログ ボックスは表示されません。

Add the Global.asax File to Your Web Application's Root Directory

図 3: Global.asax ファイルを Web アプリケーションのルート ディレクトリに追加する (クリックするとフルサイズの画像が表示されます)

規定の Global.asax ファイル テンプレートには、サーバー側の <script> タグ内に次の 5 つのメソッドが含まれています。

  • Application_Start は、Web アプリケーションが最初に起動したときに実行されます
  • Application_End は、アプリケーションのシャットダウン時に実行されます
  • Application_Error は、ハンドルされない例外がアプリケーションに到達するたびに実行されます
  • Session_Start は、新しいセッションの作成時に実行されます
  • Session_End は、セッションが期限切れまたは破棄されたときに実行されます

Application_Start イベント ハンドラーは、アプリケーションのライフ サイクル中に 1 回だけ呼び出されます。 アプリケーションは、ASP.NET リソースがアプリケーションから初めて要求されたときに起動し、アプリケーションが再起動されるまで実行を続けます。再起動は、/Bin フォルダーの内容の変更、Global.asax の変更、App_Code フォルダー内の内容の変更、または Web.config ファイルの変更などによって発生する可能性があります。 アプリケーションのライフ サイクルの詳細については、「ASP.NET アプリケーション ライフ サイクルの概要」を参照してください。

これらのチュートリアルでは、Application_Start メソッドにコードを追加するだけでよいので、それ以外のコードは削除してかまいません。 Application_Start では、StaticCache クラスの LoadStaticCache() メソッドを呼び出すだけで、サプライヤー情報が読み込まれ、キャッシュされます。

<%@ Application Language="C#" %>
<script runat="server">
    void Application_Start(object sender, EventArgs e) 
    {
        StaticCache.LoadStaticCache();
    }
</script>

これですべて完了です。 アプリケーションの起動時に、LoadStaticCache() メソッドが BLL からサプライヤー情報を取得して、これを静的メンバー変数 (または StaticCache クラスで最後に使用した何かしらのキャッシュ ストア) に保存します。 この動作を確認するには、Application_Start メソッドにブレークポイントを設定し、アプリケーションを実行します。 ブレークポイントは、アプリケーションの起動時にヒットすることに注意してください。 ただし、後続の要求では Application_Start メソッドは実行されません。

Use a Breakpoint to Verify that the Application_Start Event Handler is Being Executed

図 4: ブレークポイントを使用して Application_Start イベント ハンドラーが実行されていることを確認する (クリックするとフルサイズの画像が表示されます)

Note

最初にデバッグを開始するときに Application_Start ブレークポイントにヒットしない場合、その理由はアプリケーションが既に開始されているためです。 Global.asax または Web.config ファイルを変更して、アプリケーションを強制的に再起動してからやり直します。 これらのファイルのいずれかの末尾で空白行を追加 (または削除) するだけで、アプリケーションをすぐに再起動できます。

手順 5: キャッシュされたデータを表示する

この時点で、StaticCache クラスには、GetSuppliers() メソッドを使用してアクセスできる、アプリケーションの起動時にキャッシュされたサプライヤー データのバージョンがあります。 プレゼンテーション レイヤーからこのデータを操作するには、ObjectDataSource を使用するか、ASP.NET ページの分離コード クラスから StaticCache クラスの GetSuppliers() メソッドをプログラムで呼び出します。 ObjectDataSource および GridView コントロールを使用して、キャッシュされたサプライヤー情報を表示する方法を見てみましょう。

まず、Caching フォルダーの AtApplicationStartup.aspx ページを開きます。 GridView をツールボックスからデザイナーにドラッグし、その ID プロパティを Suppliers に設定します。 次に、GridView のスマート タグから、SuppliersCachedDataSource という名前の新しい ObjectDataSource の作成を選択します。 StaticCache クラスの GetSuppliers() メソッドを使用するように ObjectDataSource を構成します。

Configure the ObjectDataSource to use the StaticCache Class

図 5: StaticCache クラスを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)

Use the GetSuppliers() Method to Retrieve the Cached Supplier Data

図 6: GetSuppliers() メソッドを使用してキャッシュされたサプライヤー データを取得する (クリックするとフルサイズの画像が表示されます)

ウィザードが完了すると、Visual Studio によって、SuppliersDataTable 内の各データ フィールドに BoundField が自動的に追加されます。 GridView と ObjectDataSource の宣言型マークアップは次のようになります。

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="SupplierID" DataSourceID="SuppliersCachedDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="SupplierID" />
        <asp:BoundField DataField="CompanyName" HeaderText="CompanyName" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="Address" HeaderText="Address" 
            SortExpression="Address" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
        <asp:BoundField DataField="Phone" HeaderText="Phone" 
            SortExpression="Phone" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="SuppliersCachedDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliers" TypeName="StaticCache" />

図 7 は、ページをブラウザーで表示した場合の画面を示しています。 出力は、BLL の SuppliersBLL クラスからデータをプルする場合と同じですが、StaticCache クラスを使用すると、アプリケーションの起動時にキャッシュされたサプライヤー データが返されます。 StaticCache クラスの GetSuppliers() メソッドにブレークポイントを設定して、この動作を確認できます。

The Cached Supplier Data is Displayed in a GridView

図 7: キャッシュされたサプライヤー データが GridView に表示されている (クリックするとフルサイズの画像が表示されます)

まとめ

ほぼすべてのデータ モデルにはかなりの量の静的データが含まれており、これらは通常、ルックアップ テーブルの形式で実装されます。 この情報は静的であるため、この情報を表示するたびごとにデータベースに継続的にアクセスする理由はありません。 また、その静的な性質上、データをキャッシュするときに有効期限を設定する必要はありません。 このチュートリアルでは、このようなデータを取得し、データ キャッシュ、アプリケーション状態、静的メンバー変数を使用してこれをキャッシュする方法について説明しました。 この情報は、アプリケーションの起動時にキャッシュされ、アプリケーションの有効期間を通じてキャッシュに残ります。

このチュートリアルと過去 2 つのチュートリアルでは、アプリケーションの有効期間の間のデータのキャッシュと、時間ベースの有効期限の使用について説明しました。 ただ、データベースのデータをキャッシュする場合で、時間ベースの有効期限の使用が理想的ではないことがあります。 キャッシュを定期的にフラッシュするのではなく、基になるデータベース データが変更されたときにのみキャッシュされた項目を削除するのが最適です。 この理想的な手法は、次のチュートリアルで説明する SQL キャッシュの依存関係を使用して実現できます。

プログラミングに満足!

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Teresa Murphy と Zack Jones でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。