SQL キャッシュ依存関係を使用する (C#)

作成者: Scott Mitchell

PDF のダウンロード

最も簡単なキャッシュ戦略は、キャッシュされたデータを指定した期間の後に期限切れにすることです。 ただし、この単純な方法は、キャッシュされたデータが基になるデータ ソースとの関連付けを維持しないため、古いデータの保持期間が長くなったり、現在のデータがすぐに期限切れになったりします。 より良い方法は、SqlCacheDependency クラスを使用して、基になるデータが SQL データベースで変更されるまでデータがキャッシュされたままになるようにすることです。 このチュートリアルでは、その方法を説明します。

はじめに

ObjectDataSource を使用してデータをキャッシュするチュートリアルとアーキテクチャでデータをキャッシュするチュートリアルで説明したキャッシュ手法では、時間ベースの有効期限を使用して、指定した期間が経過した後にキャッシュからデータを削除しました。 この方法は、キャッシュのパフォーマンス上の利点とデータの陳腐化とのバランスを取る最も簡単な方法です。 ページ開発者は、x 秒の有効期限を選択することで、キャッシュのパフォーマンス上の利点を x 秒だけ享受することを認めつつも、データが最大 x 秒を超えて古くなることはないので安心できます。 もちろん、静的データの場合は、「アプリケーション起動時にデータをキャッシュする」チュートリアルで説明したように、x は Web アプリケーションの有効期間まで拡張できます。

データベース データをキャッシュする際、多くの場合、時間ベースの有効期限が使いやすいという理由で選択されますが、不十分なソリューションであることがよくあります。 理想は、基になるデータがデータベースで変更されるまで、データベース データはキャッシュされたままになり、その後でのみキャッシュが削除されることです。 この方法では、キャッシュのパフォーマンス上の利点を最大化し、古いデータの期間を最小限に抑えます。 ただし、これらの利点を享受するには、基になるデータベース データがいつ変更されたかを認識し、対応する項目をキャッシュから削除するシステムが存在する必要があります。 ASP.NET 2.0 より前は、ページ開発者がこのシステムの実装を担当していました。

ASP.NET 2.0 では、SqlCacheDependency クラスと、対応するキャッシュされた項目を削除できるようにデータベースで変更がいつ発生したかを判断するために必要なインフラストラクチャが用意されています。 基になるデータがいつ変更されたかを判断するには、通知とポーリングの 2 つの手法があります。 通知とポーリングの違いについて説明した後、ポーリングをサポートするために必要なインフラストラクチャを作成し、宣言型シナリオとプログラムによるシナリオで SqlCacheDependency クラスを使用する方法について説明します。

通知とポーリングについて

データベース内のデータがいつ変更されたかを判断するには、通知とポーリングという 2 つの手法を使用できます。 通知を使用すると、特定のクエリが最後に実行された後にそのクエリの結果が変更されたときに、データベースによって ASP.NET ランタイムに自動的にアラートが送信され、その時点でクエリに関連付けられているキャッシュされた項目が削除されます。 ポーリングでは、データベース サーバーは特定のテーブルが最後に更新された日時に関する情報を保持します。 ASP.NET ランタイムは、データベースを定期的にポーリングして、キャッシュに入力されてから変更されたテーブルを確認します。 データが変更されたテーブルは、関連するキャッシュ項目が削除されます。

通知オプションはポーリングよりも少ないセットアップで済み、テーブル レベルではなくクエリ レベルで変更を追跡するため、より細かく設定できます。 残念ながら、通知は Microsoft SQL Server 2005 の完全版 (つまり、Express 以外のエディション) でしか使用できません。 ただし、ポーリング オプションは、7.0 から 2005 までの Microsoft SQL Server のすべてのバージョンで使用できます。 これらのチュートリアルでは SQL Server 2005 の Express エディションを使用するため、ポーリング オプションの設定と使用に重点を置きます。 SQL Server 2005 の通知機能に関するその他のリソースについては、このチュートリアルの最後にある「さらに読む」セクションをご覧ください。

ポーリングでは、データベースに 3 つの列 (tableNamenotificationCreatedchangeId) を含む AspNet_SqlCacheTablesForChangeNotification という名前のテーブルを含むように構成する必要があります。 このテーブルには、Web アプリケーションの SQL キャッシュ依存関係で使用する必要がある可能性があるデータを含む各テーブルの行が含まれています。 tableName 列はテーブルの名前を指定し、notificationCreated は行がテーブルに追加された日時を示します。 changeId 列は int 型であり、初期値は 0 です。 その値は、テーブルに変更が加えられるたびにインクリメントされます。

データベースには、AspNet_SqlCacheTablesForChangeNotification テーブルに加えて、SQL キャッシュの依存関係に現れる可能性がある各テーブルのトリガーも含める必要があります。 これらのトリガーは、行が挿入、更新、または削除されるたびに実行され、AspNet_SqlCacheTablesForChangeNotification でテーブルの changeId 値がインクリメントされます。

ASP.NET ランタイムは、SqlCacheDependency オブジェクトを使用してデータをキャッシュするときに、テーブルの現在の changeId を追跡します。 データベースは定期的にチェックされ、changeId がデータベース内の値と異なる SqlCacheDependency オブジェクトは削除されます。これは、changeId 値が異なる場合、データがキャッシュされてからテーブルが変更されたことを示すためです。

手順 1: aspnet_regsql.exe コマンド ライン プログラムを探索する

ポーリング手法では、前述のインフラストラクチャを含むデータベースをセットアップする必要があります (定義済みのテーブル (AspNet_SqlCacheTablesForChangeNotification)、いくつかのストアド プロシージャ、および Web アプリケーションの SQL キャッシュ依存関係で使用できる各テーブルのトリガー)。 これらのテーブル、ストアド プロシージャ、およびトリガーは、$WINDOWS$\Microsoft.NET\Framework\version フォルダーにあるコマンド ライン プログラム aspnet_regsql.exe を使用して作成できます。 AspNet_SqlCacheTablesForChangeNotification テーブルと関連するストアド プロシージャを作成するには、コマンド ラインから次を実行します。

/* For SQL Server authentication... */
aspnet_regsql.exe -S server -U user -P password -d database -ed
/* For Windows Authentication... */
aspnet_regsql.exe -S server -E -d database -ed

Note

これらのコマンドを実行するには、指定されたデータベース ログインが db_securityadmin および db_ddladmin ロールに含まれている必要があります。

たとえば、Windows 認証を使用する ScottsServer という名前のデータベース サーバー上の pubs という名前の Microsoft SQL Server データベースにポーリング用のインフラストラクチャを追加するには、適切なディレクトリに移動し、コマンド ラインから次のように入力します。

aspnet_regsql.exe -S ScottsServer -E -d pubs -ed

データベース レベルのインフラストラクチャが追加されたら、SQL キャッシュの依存関係で使用されるテーブルにトリガーを追加する必要があります。 aspnet_regsql.exe コマンド ライン プログラムをもう一度使用しますが、-t スイッチを使用してテーブル名を指定し、次のように -ed スイッチを使用する代わりに -et を使用します。

/* For SQL Server authentication... */
aspnet_regsql.exe -S <i>server</i>
-U <i>user</i> -P <i>password</i> -d <i>database</i> -t <i>tableName</i> -et
/* For Windows Authentication... */
aspnet_regsql.exe -S <i>server</i>
-E -d <i>database</i> -t <i>tableName</i> -et

ScottsServerpubs データベースの authors テーブルと titles テーブルにトリガーを追加するには、次のようにします。

aspnet_regsql.exe -S ScottsServer -E -d pubs -t authors -et
aspnet_regsql.exe -S ScottsServer -E -d pubs -t titles -et

このチュートリアルでは、トリガーを ProductsCategoriesSuppliers テーブルに追加します。 手順 3 では、特定のコマンド ライン構文について説明します。

手順 2: Microsoft SQL Server 2005 Express Edition の App_Data のデータベースを参照する

aspnet_regsql.exe コマンド ライン プログラムでは、必要なポーリング インフラストラクチャを追加するために、データベースとサーバー名が必要です。 しかし、App_Data フォルダーに存在する Microsoft SQL Server 2005 Express データベースのデータベースとサーバー名が何か分かりますか? データベース名とサーバー名を検出する必要はなく、SQL Server Management Studio を使用してデータベースを localhost\SQLExpress データベース インスタンスにアタッチし、データの名前を変更するのが最も簡単な方法であることがわかりました。 コンピューターに SQL Server 2005 の完全なバージョンのいずれかがインストールされている場合は、コンピューターに SQL Server Management Studio が既にインストールされている可能性があります。 Express エディションのみを使用している場合は、無料の Microsoft SQL Server Management Studio Express Edition をダウンロードできます。

まず、Visual Studio を閉じます。 次に、SQL Server Management Studio を開き、Windows 認証を使用して localhost\SQLExpress サーバーに接続することを選択します。

Attach to the localhost\SQLExpress Server

図 1: localhost\SQLExpress サーバーに接続する

サーバーに接続すると、Management Studio にサーバーが表示され、データベースやセキュリティなどのサブフォルダーが表示されます。 [データベース] フォルダーを右クリックし、[アタッチ] オプションを選択します。 [データベースのアタッチ] ダイアログ ボックスが表示されます (図 2 を参照)。 [追加] ボタンをクリックし、Web アプリケーションの App_Data フォルダー内の NORTHWND.MDF データベース フォルダーを選択します。

Attach the NORTHWND.MDF Database from the App_Data Folder

図 2: App_Data フォルダーから NORTHWND.MDF データベースをアタッチする (クリックするとフルサイズの画像が表示されます)

データベースが [データベース] フォルダーに追加されます。 データベース名は、データベース ファイルへの完全なパス、または GUID で始まる完全なパスである場合があります。 aspnet_regsql.exe コマンド ライン ツールを使用するときに、この長いデータベース名を入力しなくてもよいように、アタッチしたデータベースを右クリックし、[名前の変更] を選択して、データベースの名前をわかりやすい名前に変更します。 ここでは、データベースの名前を DataTutorials に変更しました。

Rename the Attached Database to a More Human-Friendly Name

図 3: アタッチされたデータベースの名前をよりわかりやすい名前に変更する

手順 3: Northwind データベースにポーリング インフラストラクチャを追加する

App_Data フォルダーから NORTHWND.MDF データベースをアタッチしたので、ポーリング インフラストラクチャを追加する準備ができました。 データベースの名前を DataTutorials に変更したと仮定して、次の 4 つのコマンドを実行します。

aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -ed
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Products -et
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Categories -et
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Suppliers -et

これらの 4 つのコマンドを実行した後、Management Studio でデータベース名を右クリックし、[タスク] サブメニューに移動し、[デタッチ] を選択します。 次に、Management Studio を閉じ、Visual Studio をもう一度開きます。

Visual Studio が再度開いたら、サーバー エクスプローラーを使用してデータベースにドリルダウンします。 新しいテーブル (AspNet_SqlCacheTablesForChangeNotification)、新しいストアド プロシージャ、および ProductsCategoriesSuppliers テーブルのトリガーをメモします。

The Database Now Includes the Necessary Polling Infrastructure

図 4: データベースに必要なポーリング インフラストラクチャが含まれるようになりました

手順 4: ポーリング サービスを構成する

必要なテーブル、トリガー、ストアド プロシージャをデータベースに作成したら、最後にポーリング サービスを構成します。Web.config を使用して、使用するデータベースとポーリング頻度をミリ秒単位で指定します。 次のマークアップは、Northwind データベースを 1 秒に 1 回ポーリングします。

<?xml version="1.0"?>
<configuration>
   <connectionStrings>
      <add name="NORTHWNDConnectionString" connectionString=
          "Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;
           Integrated Security=True;User Instance=True" 
           providerName="System.Data.SqlClient"/>
   </connectionStrings>
   <system.web>
      ...
      <!-- Configure the polling service used for SQL cache dependencies -->
      <caching>
         <sqlCacheDependency enabled="true" pollTime="1000" >
            <databases>
               <add name="NorthwindDB" 
                    connectionStringName="NORTHWNDConnectionString" />
            </databases>
         </sqlCacheDependency>
      </caching>
   </system.web>
</configuration>

<add> 要素 (NorthwindDB) の name 値は、人間が判読できる名前を特定のデータベースに関連付けます。 SQL キャッシュの依存関係を使用する場合は、ここで定義されているデータベース名と、キャッシュされたデータの基になるテーブルを参照する必要があります。 手順 6 では、SqlCacheDependency クラスを使用して SQL キャッシュの依存関係をキャッシュされたデータにプログラムで関連付ける方法について説明します。

SQL キャッシュの依存関係が確立されると、ポーリング システムは pollTime ミリ秒ごとに <databases> 要素で定義されているデータベースに接続し、AspNet_SqlCachePollingStoredProcedure ストアド プロシージャを実行します。 このストアド プロシージャ (aspnet_regsql.exe コマンド ライン ツールを使用して手順 3 で追加したもの) は、AspNet_SqlCacheTablesForChangeNotification の各レコードの tableNamechangeId 値を返します。 古い SQL キャッシュの依存関係がキャッシュから削除されます。

pollTime 設定では、パフォーマンスとデータの陳腐化とのトレードオフが生じます。 pollTime 値を小さくすると、データベースへの要求の数が増えますが、キャッシュから古いデータがより迅速に削除されます。 pollTime 値を大きくするとデータベース要求の数が減りますが、バックエンド データが変更されてから、関連するキャッシュ項目が削除されるまでの遅延が長くなります。 幸いなことに、データベース要求では、単純な軽量テーブルから数行のみを返す単純なストアド プロシージャが実行されています。 ただし、さまざまな pollTime 値を試して、アプリケーションのデータベース アクセスとデータの陳腐化との間の理想的なバランスを見つけてください。 使用できる pollTime の最小値は 500 です。

Note

上記の例では、<sqlCacheDependency> 要素に 1 つの pollTime 値を指定していますが、必要に応じて pollTime 要素に <add> 値を指定できます。 これは、複数のデータベースを指定し、データベースごとのポーリング頻度をカスタマイズする場合に便利です。

手順 5: SQL キャッシュの依存関係を宣言的に操作する

手順 1 から 4 では、必要なデータベース インフラストラクチャをセットアップし、ポーリング システムを構成する方法について説明しました。 このインフラストラクチャが整ったので、プログラムまたは宣言型の手法を使用して、関連する SQL キャッシュの依存関係を持つ項目をデータ キャッシュに追加できるようになりました。 この手順では、SQL キャッシュの依存関係を宣言的に操作する方法について説明します。 手順 6 では、プログラムによる手法について説明します。

ObjectDataSource を使用してデータをキャッシュするチュートリアルでは、ObjectDataSource の宣言型キャッシュ機能について説明しました。 EnableCaching プロパティを true に、CacheDuration プロパティをある時間間隔に設定するだけで、ObjectDataSource は指定された間隔で基になるオブジェクトから返されたデータを自動的にキャッシュします。 ObjectDataSource では、1 つ以上の SQL キャッシュ依存関係を使用することもできます。

SQL キャッシュの依存関係を宣言的に使用する方法を示すには、Caching フォルダー内の SqlCacheDependencies.aspx ページを開き、ツールボックスからデザイナーに GridView をドラッグします。 GridView の IDProductsDeclarative に設定し、スマート タグから ProductsDataSourceDeclarative という名前の新しい ObjectDataSource にバインドすることを選択します。

Create a New ObjectDataSource Named ProductsDataSourceDeclarative

図 5: ProductsDataSourceDeclarative という名前の新しい ObjectDataSource を作成します (クリックするとフルサイズの画像が表示されます)

ProductsBLL クラスを使用するように ObjectDataSource を構成し、[SELECT] タブのドロップダウン リストを GetProducts() に設定します。 [UPDATE] タブで、3 つの入力パラメーター (productNameunitPriceproductID) を含む UpdateProduct オーバーロードを選択します。 [INSERT] タブと [DELETE] タブでドロップダウン リストを (なし) に設定します。

Use the UpdateProduct Overload with Three Input Parameters

図 6: 3 つの入力パラメーターを含む UpdateProduct オーバーロードを使用する (クリックするとフルサイズの画像が表示されます)

Set the Drop-Down List to (None) for the INSERT and DELETE Tabs

図 7: INSERT タブと DELETE タブのドロップダウン リストを (なし) に設定する (クリックするとフルサイズの画像が表示されます)

データ ソースの構成ウィザードが完了すると、Visual Studio によって各データ フィールドの BoundField と CheckBoxField が GridView に作成されます。 ProductNameCategoryNameUnitPrice を除くすべてのフィールドを削除し、これらのフィールドを必要に応じて書式設定します。 GridView のスマート タグで、[ページングを有効にする]、[並べ替えを有効にする]、[編集を有効にする] チェック ボックスをオンにします。 Visual Studio では、ObjectDataSource の OldValuesParameterFormatString プロパティが original_{0} に設定されます。 GridView の編集機能が正常に動作するためには、宣言構文からこのプロパティを完全に削除するか、既定値 ({0}) に戻します。

最後に、GridView の上に Label Web コントロールを追加し、その ID プロパティを ODSEvents に、EnableViewState プロパティを false に設定します。 これらの変更を行った後、ページの宣言型マークアップは次のようになります。 GridView フィールドに対して、SQL キャッシュの依存関係機能を示すために必要ではないいくつかの美的なカスタマイズを行っていることに注意してください。

<asp:Label ID="ODSEvents" runat="server" EnableViewState="False" />
<asp:GridView ID="ProductsDeclarative" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSourceDeclarative" 
    AllowPaging="True" AllowSorting="True">
    <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <EditItemTemplate>
                <asp:TextBox ID="ProductName" runat="server" 
                    Text='<%# Bind("ProductName") %>' />
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                    ControlToValidate="ProductName" Display="Dynamic" 
                    ErrorMessage="You must provide a name for the product." 
                    SetFocusOnError="True"
                    runat="server">*</asp:RequiredFieldValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <EditItemTemplate>
                $<asp:TextBox ID="UnitPrice" runat="server" Columns="8" 
                    Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator1" runat="server" 
                    ControlToValidate="UnitPrice"
                    ErrorMessage="You must enter a valid currency value with 
                        no currency symbols. Also, the value must be greater than 
                        or equal to zero."
                    Operator="GreaterThanEqual" SetFocusOnError="True" 
                    Type="Currency" Display="Dynamic" 
                    ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("UnitPrice", "{0:c}") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSourceDeclarative" runat="server" 
    SelectMethod="GetProducts" TypeName="ProductsBLL" 
    UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

次に、ObjectDataSource の Selecting イベントのイベント ハンドラーを作成し、その中に次のコードを追加します。

protected void ProductsDataSourceDeclarative_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    ODSEvents.Text = "-- Selecting event fired";
}

ObjectDataSource の Selecting イベントは、基になるオブジェクトからデータを取得するときにのみ発生することを思い出してください。 ObjectDataSource が独自のキャッシュからデータにアクセスした場合、このイベントは発生しません。

次に、ブラウザーからこのページにアクセスします。 キャッシュはまだ実装していないため、グリッドをページング、並べ替え、または編集するたびに、図 8 に示すように、"Selecting event fired" というテキストがページに表示されます。

The ObjectDataSource s Selecting Event Fires Each Time the GridView is Paged, Edited, or Sorted

図 8: ObjectDataSource の Selecting イベントは、GridView のページング、編集、または並べ替えが行われるたびに発生します (クリックするとフルサイズの画像が表示されます)

ObjectDataSource を使用してデータをキャッシュするチュートリアルで説明したように、EnableCaching プロパティを true に設定すると、ObjectDataSource は CacheDuration プロパティで指定された期間、そのデータをキャッシュします。 ObjectDataSource には SqlCacheDependency プロパティもあり、このパターンを使用してキャッシュされたデータに 1 つ以上の SQL キャッシュの依存関係を追加します。

databaseName1:tableName1;databaseName2:tableName2;...

ここで databaseNameWeb.config<add> 要素の name 属性に指定されたデータベースの名前、tableName はデータベース テーブルの名前です。 たとえば、Northwind の Products テーブルに対する SQL キャッシュの依存関係に基づいて無期限にデータをキャッシュする ObjectDataSource を作成するには、ObjectDataSource の EnableCaching プロパティを true に、SqlCacheDependency プロパティを NorthwindDB:Products に設定します。

Note

SQL キャッシュの依存関係、および時間ベースの有効期限を使用するには、EnableCachingtrue に、CacheDuration を時間間隔に、SqlCacheDependency をデータベースとテーブル名に設定します。 ObjectDataSource は、時間ベースの有効期限に達したとき、またはポーリング システムが基になるデータベース データが変更されたことに気づいた場合にデータを削除します (どちらか早い方)。

SqlCacheDependencies.aspx の GridView には、2 つのテーブル (ProductsCategories) からのデータが表示されます (製品の CategoryName フィールドは CategoriesJOIN を介して取得されます)。 したがって、2 つの SQL キャッシュ依存関係である NorthwindDB:Products;NorthwindDB:Categories を指定します。

Configure the ObjectDataSource to Support Caching Using SQL Cache Dependencies on Products and Categories

図 9: Products および Categories で SQL キャッシュの依存関係を使用したキャッシュをサポートするように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)

キャッシュをサポートするように ObjectDataSource を構成した後、ブラウザーを使用してページに再度アクセスします。 ここでも、"Selecting event fired" というテキストがページに最初にアクセスした際に表示されますが、ページング、並べ替え、あるいは [編集] ボタンまたは [キャンセル] ボタンをクリックすると消えます。 これは、ObjectDataSource のキャッシュにデータが読み込まれた後、Products または Categories テーブルが変更されるか、GridView を使用してデータが更新されるまで、データがそこに残るためです。

グリッドをページングし、"Selecting event fired" というテキストが表示されていないことを確認した後、新しいブラウザー ウィンドウを開き、[編集]、[挿入]、[削除] セクション (~/EditInsertDelete/Basics.aspx) の [基本] チュートリアルに移動します。 製品の名前または価格を更新します。 最初のブラウザー ウィンドウから、データの別のページを表示したり、グリッドを並べ替えたり、行の [編集] ボタンをクリックしたりします。 今回は、基になるデータベース データが変更されたため、"Selecting event fired" が再び表示されます (図 10 を参照)。 テキストが表示されない場合は、しばらく待ってからやり直してください。 ポーリング サービスでは、Products テーブルの変更が pollTime ミリ秒ごとにチェックされるため、基になるデータが更新されてからキャッシュされたデータが削除されるまでに遅延が発生します。

Modifying the Products Table Evicts the Cached Product Data

図 10: 製品テーブルを変更すると、キャッシュされた製品データが削除されます (クリックするとフルサイズの画像が表示されます)

手順 6: SqlCacheDependency クラスをプログラムで操作する

アーキテクチャでデータをキャッシュするチュートリアルでは、キャッシュと ObjectDataSource を密に結合するのではなく、アーキテクチャで別のキャッシュ レイヤーを使用する利点について説明しました。 そのチュートリアルでは、データ キャッシュをプログラムで操作する方法を示す ProductsCL クラスを作成しました。 キャッシュ レイヤーで SQL キャッシュの依存関係を利用するには、SqlCacheDependency クラスを使用します。

ポーリング システムでは、SqlCacheDependency オブジェクトを特定のデータベースとテーブルのペアに関連付ける必要があります。 たとえば、次のコードでは、Northwind データベースの Products テーブルに基づいて SqlCacheDependency オブジェクトを作成します。

Caching.SqlCacheDependency productsTableDependency = 
    new Caching.SqlCacheDependency("NorthwindDB", "Products");

SqlCacheDependency のコンストラクターに対する 2 つの入力パラメーターは、それぞれデータベース名とテーブル名です。 ObjectDataSource の SqlCacheDependency プロパティと同様に、使用されるデータベース名は、Web.config<add> 要素の name 属性で指定された値と同じです。 テーブル名は、データベース テーブルの実際の名前です。

SqlCacheDependency をデータ キャッシュに追加された項目に関連付けるには、依存関係を受け入れる Insert メソッド オーバーロードのいずれかを使用します。 次のコードは、無期限でデータ キャッシュに "値" を追加しますが、Products テーブル上の SqlCacheDependency と関連付けます。 要するに、"値" は、メモリ制約のために削除されるまで、または Products テーブルがキャッシュされてから変更されたことがポーリング システムによって検出されるまでキャッシュに残ります。

Caching.SqlCacheDependency productsTableDependency = 
    new Caching.SqlCacheDependency("NorthwindDB", "Products");
Cache.Insert(key, 
             value, 
             productsTableDependency, 
             System.Web.Caching.Cache.NoAbsoluteExpiration, 
             System.Web.Caching.Cache.NoSlidingExpiration);

現在、キャッシュ レイヤーの ProductsCL クラスは、60 秒の時間ベースの有効期限を使用して Products テーブルのデータをキャッシュしています。 代わりに SQL キャッシュの依存関係を使用するように、このクラスを更新しましょう。 キャッシュにデータを追加する ProductsCL クラスの AddCacheItem メソッドには、現在、次のコードが含まれています。

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Make sure MasterCacheKeyArray[0] is in the cache
    DataCache[MasterCacheKeyArray[0]] = DateTime.Now;
    // Add a CacheDependency
    Caching.CacheDependency dependency =
        new Caching.CacheDependency(null, MasterCacheKeyArray);
    DataCache.Insert(GetCacheKey(rawKey), value, dependency, 
        DateTime.Now.AddSeconds(CacheDuration), 
        System.Web.Caching.Cache.NoSlidingExpiration);
}

MasterCacheKeyArray キャッシュの依存関係の代わりに SqlCacheDependency オブジェクトを使用するように、このコードを更新します。

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Add the SqlCacheDependency objects for Products
    Caching.SqlCacheDependency productsTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Products");
    // Add the item to the data cache using productsTableDependency
    DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, 
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration);
}

この機能をテストするには、既存 ProductsDeclarative GridView の下のページに GridView を追加します。 スマート タグから、この新しい GridView の IDProductsProgrammatic に設定し、ProductsDataSourceProgrammatic という名前の新しい ObjectDataSource にバインドします。 ProductsCL クラスを使用するように ObjectDataSource を構成し、[SELECT] タブと [UPDATE] タブのドロップダウン リストをそれぞれ GetProductsUpdateProduct に設定します。

Configure the ObjectDataSource to Use the ProductsCL Class

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

Select the GetProducts Method from the SELECT Tab s Drop-Down List

図 12: [SELECT] タブのドロップダウン リストから GetProducts メソッドを選択する (クリックするとフルサイズの画像が表示されます)

Choose the UpdateProduct Method from the UPDATE Tab s Drop-Down List

図 13: [UPDATE] タブのドロップダウン リストから UpdateProduct メソッドを選択する (クリックするとフルサイズの画像が表示されます)

データ ソースの構成ウィザードが完了すると、Visual Studio によって各データ フィールドの BoundField と CheckBoxField が GridView に作成されます。 このページに追加された最初の GridView と同様に、ProductNameCategoryNameUnitPrice 以外のすべてのフィールドを削除し、必要に応じてこれらのフィールドを書式設定します。 GridView のスマート タグで、[ページングを有効にする]、[並べ替えを有効にする]、[編集を有効にする] チェック ボックスをオンにします。 ProductsDataSourceDeclarative ObjectDataSource と同様に、Visual Studio は ProductsDataSourceProgrammatic ObjectDataSource の OldValuesParameterFormatString プロパティを original_{0} に設定します。 GridView の編集機能が正常に動作するためには、このプロパティを {0} に戻します (または、宣言構文からプロパティの割り当てを完全に削除します)。

これらのタスクを完了すると、結果として得られる GridView および ObjectDataSource の宣言型マークアップは次のようになります。

<asp:GridView ID="ProductsProgrammatic" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSourceProgrammatic" AllowPaging="True" 
    AllowSorting="True">
    <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <EditItemTemplate>
                <asp:TextBox ID="ProductName" runat="server" 
                    Text='<%# Bind("ProductName") %>' />
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1"  
                    ControlToValidate="ProductName" Display="Dynamic" 
                    ErrorMessage="You must provide a name for the product." 
                    SetFocusOnError="True"
                    runat="server">*</asp:RequiredFieldValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <EditItemTemplate>
                $<asp:TextBox ID="UnitPrice" runat="server" Columns="8" 
                    Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator1" runat="server" 
                    ControlToValidate="UnitPrice" Display="Dynamic" 
                    ErrorMessage="You must enter a valid currency value with 
                        no currency symbols. Also, the value must be greater than 
                        or equal to zero."
                    Operator="GreaterThanEqual" SetFocusOnError="True" 
                    Type="Currency" ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("UnitPrice", "{0:c}") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSourceProgrammatic" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetProducts" 
    TypeName="ProductsCL" UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

キャッシュ レイヤーで SQL キャッシュの依存関係をテストするには、ProductCL クラスの AddCacheItem メソッドにブレークポイントを設定し、デバッグを開始します。 SqlCacheDependencies.aspx に最初にアクセスすると、データが初めて要求され、キャッシュに配置されるため、ブレークポイントにヒットします。 次に、GridView 内の別のページに移動するか、列の 1 つを並べ替えます。 これにより、GridView はデータの再クエリを実行しますが、Products データベース テーブルが変更されていないため、データはキャッシュにあります。 データがキャッシュに繰り返し見つからない場合は、コンピューターに十分なメモリがあることを確認してから、もう一度やり直してください。

GridView のいくつかのページをページングした後、2 番目のブラウザー ウィンドウを開き、[編集]、[挿入]、[削除] セクション (~/EditInsertDelete/Basics.aspx) の [基本] チュートリアルに移動します。 Products テーブルからレコードを更新し、最初のブラウザー ウィンドウで新しいページを表示するか、並べ替えヘッダーのいずれかをクリックします。

このシナリオで見られる結果は次の 2 つのいずれかです。ブレークポイントにヒットする場合は、データベースの変更によってキャッシュされたデータが削除されたことを示します。ブレークポイントにヒットしない場合は、SqlCacheDependencies.aspx に古いデータが表示されていることを意味します。 ブレークポイントにヒットしない場合は、データが変更されてからポーリング サービスがまだ起動されていないことが原因である可能性があります。 ポーリング サービスでは、Products テーブルの変更が pollTime ミリ秒ごとにチェックされるため、基になるデータが更新されてからキャッシュされたデータが削除されるまでに遅延が発生します。

Note

この遅延は、SqlCacheDependencies.aspx で GridView を使用していずれかの製品を編集するときに見られる可能性が高くなります。 アーキテクチャでデータをキャッシュするチュートリアルでは、ProductsCL クラスの UpdateProduct メソッドを使用して編集されているデータがキャッシュから削除されるように、MasterCacheKeyArray キャッシュの依存関係を追加しました。 ただし、この手順の前に AddCacheItem メソッドを変更するときに、このキャッシュ依存関係を置き換えたため、ProductsCL クラスは、ポーリング システムが Products テーブルへの変更に気が付くまで、キャッシュされたデータを表示し続けます。 手順 7 で MasterCacheKeyArray キャッシュの依存関係を再導入する方法について説明します。

手順 7: キャッシュされた項目に複数の依存関係を関連付ける

MasterCacheKeyArray キャッシュの依存関係は、その中に関連付けられている 1 つの項目が更新されたときに、すべての製品関連データがキャッシュから削除されるようにするために使用されることを思い出してください。 たとえば、GetProductsByCategoryID(categoryID) メソッドは一意の categoryID 値ごとに ProductsDataTables インスタンスをキャッシュします。 これらのオブジェクトのいずれかが削除された場合、MasterCacheKeyArray キャッシュの依存関係により、他のオブジェクトも確実に削除されます。 このキャッシュ依存関係がないと、キャッシュされたデータが変更されたときに、他のキャッシュされた製品データが古くなっている可能性があります。 したがって、SQL キャッシュの依存関係を使用する場合は、MasterCacheKeyArray キャッシュの依存関係を維持することが重要です。 ただし、データ キャッシュの Insert メソッドでは、1 つの依存関係オブジェクトのみが許可されます。

さらに、SQL キャッシュの依存関係を操作する場合は、複数のデータベース テーブルを依存関係として関連付ける必要がある場合があります。 たとえば、ProductsCL クラスにキャッシュされた ProductsDataTable には各製品のカテゴリ名とサプライヤー名が含まれていますが、AddCacheItem メソッドでは Products への依存関係のみを使用します。 この状況では、ユーザーがカテゴリまたはサプライヤーの名前を更新した場合、キャッシュされた製品データはキャッシュに残り、古くなります。 そのため、キャッシュされた製品データを Products テーブルだけでなく、Categories テーブルや Suppliers テーブルにも依存させる必要があります。

AggregateCacheDependency クラスは、複数の依存関係をキャッシュ項目に関連付けるための手段を提供します。 まず、AggregateCacheDependency インスタンスを作成します。 次に、AggregateCacheDependencyAdd メソッドを使用して依存関係のセットを追加します。 その後、データ キャッシュに項目を挿入するときに、AggregateCacheDependency インスタンスを渡します。 AggregateCacheDependency インスタンスの依存関係のいずれかが変更されると、キャッシュされた項目は削除されます。

ProductsCL クラスの AddCacheItem メソッドの更新されたコードを次に示します。 このメソッドでは、MasterCacheKeyArray キャッシュの依存関係が、ProductsCategoriesSuppliers テーブルの SqlCacheDependency オブジェクトと共に作成されます。 これらはすべて、Insert メソッドに渡される aggregateDependencies という名前の 1 つの AggregateCacheDependency オブジェクトに結合されます。

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Make sure MasterCacheKeyArray[0] is in the cache and create a depedency
    DataCache[MasterCacheKeyArray[0]] = DateTime.Now;
    Caching.CacheDependency masterCacheKeyDependency = 
        new Caching.CacheDependency(null, MasterCacheKeyArray);
    // Add the SqlCacheDependency objects for Products, Categories, and Suppliers
    Caching.SqlCacheDependency productsTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Products");
    Caching.SqlCacheDependency categoriesTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Categories");
    Caching.SqlCacheDependency suppliersTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Suppliers");
    // Create an AggregateCacheDependency
    Caching.AggregateCacheDependency aggregateDependencies = 
        new Caching.AggregateCacheDependency();
    aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, 
        categoriesTableDependency, suppliersTableDependency);
    DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, 
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration);
}

この新しいコードをテストします。これで、ProductsCategories、または Suppliers テーブルに変更を加えると、キャッシュされたデータが削除されます。 さらに、GridView を使用して製品を編集するときに呼び出される ProductsCL クラスの UpdateProduct メソッドは、MasterCacheKeyArray キャッシュの依存関係を削除します。これにより、キャッシュされた ProductsDataTable が削除され、次の要求でデータが再取得されます。

Note

SQL キャッシュの依存関係は、出力キャッシュと共に使用することもできます。 この機能のデモについては、「SQL Server で ASP.NET 出力キャッシュを使用する」をご覧ください。

まとめ

データベース データをキャッシュする場合、データはデータベース内で変更されるまでキャッシュに残るのが理想的です。 ASP.NET 2.0 では、SQL キャッシュの依存関係を作成し、宣言型とプログラム型の両方のシナリオで使用できます。 このアプローチの課題の 1 つは、データがいつ変更されたかを検出することです。 Microsoft SQL Server 2005 の完全なバージョンでは、クエリ結果が変更されたときにアプリケーションに警告できる通知機能が提供されます。 SQL Server 2005 の Expressエディションおよび以前のバージョンの SQL Server では、代わりにポーリング システムを使用する必要があります。 幸い、必要なポーリング インフラストラクチャの設定は非常に簡単です。

プログラミングに満足!

もっと読む

この記事で説明したトピックの詳細については、次のリソースを参照してください。

著者について

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

特別な感謝

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