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 の通知機能の詳細については、このチュートリアルの最後にある「さらに読む」セクションを参照してください。

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

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

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

注意

これらのコマンドを実行するには、指定したデータベース ログインが ロールと db_ddladmin ロールにdb_securityadmin存在する必要があります。

たとえば、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

のデータベースの テーブルと titles テーブルにauthorsトリガーを追加するには、次の値をpubsScottsServer使用します。

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

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

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

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

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

localhost\SQLExpress Server にアタッチする

図 1: サーバーにアタッチするlocalhost\SQLExpress

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

NORTHWND をアタッチします。App_Data フォルダーからの MDF データベース

図 2: フォルダーからデータベースをNORTHWND.MDFApp_Dataアタッチする (フルサイズの画像を表示する場合はクリックします)

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

アタッチされたデータベースの名前を [その他の Human-Friendly 名] に変更する

図 3: アタッチされたデータベースの名前を、さらに Human-Friendly 名に変更する

手順 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)、新しいストアド プロシージャ、および 、Categories、および Suppliers テーブルのトリガーにProducts注意してください。

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

図 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>

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

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

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

注意

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

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

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

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

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

ProductsDataSourceDeclarative という名前の新しい ObjectDataSource を作成する

図 5: 名前付きの ProductsDataSourceDeclarative 新しい ObjectDataSource を作成する (フルサイズの画像を表示する場合はクリックします)

クラスを使用するように ObjectDataSource を ProductsBLL 構成し、[選択] タブのドロップダウン リストを に GetProducts()設定します。 [更新] タブで、および の UpdateProduct 3 つの入力パラメーターproductNameunitPriceを持つオーバーロードをproductID選択します。 [挿入] タブと [削除] タブで、ドロップダウン リストを [(なし)] に設定します。

3 つの入力パラメーターで UpdateProduct オーバーロードを使用する

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

[挿入] タブと [削除] タブの Drop-Down リストを (なし) に設定します

図 7: INSERT タブと DELETE タブの Drop-Down リストを (なし) に設定します (フルサイズの画像を表示する をクリックします)

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

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

<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 が独自のキャッシュからデータにアクセスする場合、このイベントは発生しません。

次に、ブラウザーからこのページにアクセスします。 キャッシュはまだ実装していないため、グリッドをページ、並べ替え、または編集するたびに、"Selecting event fired, as figure 8 shows.

ObjectDataSource s Selecting イベントは、GridView がページング、編集、または並べ替えられるたびに発生します

図 8: ObjectDataSource の Selecting イベントは、GridView が Paged、Edited、または Sorted されるたびに発生します (フルサイズの画像を表示する場合はクリックします)

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

databaseName1:tableName1;databaseName2:tableName2;...

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

注意

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

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

製品とカテゴリに対する SQL キャッシュ依存関係を使用したキャッシュをサポートするように ObjectDataSource を構成する

図 9: と に対する SQL キャッシュ依存関係 ProductsCategories を使用したキャッシュをサポートするように ObjectDataSource を構成する (フルサイズの画像を表示するには、ここをクリックします)

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

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

Products テーブルを変更すると、キャッシュされた製品データが削除されます

図 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);

現在、Caching Layer の ProductsCL クラスは、時間ベースの有効期限 60 秒を使用してテーブルの Products データをキャッシュします。 代わりに SQL キャッシュの依存関係を使用するように、このクラスを更新しましょう。 ProductsCLキャッシュにデータを追加するクラス s 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);
}

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

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 タブのドロップダウン リストをそれぞれ と UpdateProductGetProducts設定します。

ProductsCL クラスを使用するように ObjectDataSource を構成する

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

SELECT タブの Drop-Down リストから GetProducts メソッドを選択します

図 12: SELECT GetProducts タブの Drop-Down リストからメソッドを選択します (フルサイズの画像を表示する をクリックします)

UPDATE タブの Drop-Down リストから UpdateProduct メソッドを選択する

図 13: Update タブの Drop-Down リストから UpdateProduct メソッドを選択します (フルサイズの画像を表示する をクリックします)

データ ソースの構成ウィザードが完了すると、Visual Studio によって、各データ フィールドに対して GridView に BoundFields と CheckBoxFields が作成されます。 このページに追加された最初の GridView と同様に、、CategoryName、、 以外のすべてのフィールドProductNameを削除しUnitPrice、必要に応じてこれらのフィールドの書式を設定します。 GridView のスマート タグから、[ページングを有効にする]、[並べ替えを有効にする]、[編集を有効にする] チェック ボックスをチェックします。 ObjectDataSource と同様にProductsDataSourceDeclarative、Visual Studio は ObjectDataSource の OldValuesParameterFormatString プロパティを ProductsDataSourceProgrammaticoriginal_{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 キャッシュの依存関係をテストするには、 クラスの AddCacheItem メソッドにブレークポイントをProductCL設定し、デバッグを開始します。 に初めてアクセス SqlCacheDependencies.aspxすると、データが初めて要求され、キャッシュに配置されるため、ブレークポイントにヒットする必要があります。 次に、GridView 内の別のページに移動するか、列の 1 つを並べ替えます。 これにより、GridView はデータの再クエリを実行しますが、データベース テーブルが変更されていないため Products 、キャッシュにデータが見つかる必要があります。 キャッシュにデータが繰り返し見つからない場合は、コンピューターに十分なメモリがあることを確認してから、もう一度やり直してください。

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

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

注意

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

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

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

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

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

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

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);
}

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

注意

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

まとめ

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

幸せなプログラミング!

もっと読む

このチュートリアルで説明するトピックの詳細については、次のリソースを参照してください。

著者について

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

特別な感謝

このチュートリアル シリーズは、多くの役立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、マルコ・レンジル、テレサ・マーフィー、ヒルトン・ギーゼナウでした。 今後の MSDN 記事の確認に関心がありますか? その場合は、 に行mitchell@4GuysFromRolla.comをドロップしてください。