次の方法で共有


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

スコット・ミッチェル著

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

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

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

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

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

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

たとえば、Windows 認証を使用して pubs という名前のデータベース サーバー上の ScottsServer という名前の 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

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

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

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

localhost\SQLExpress Server にアタッチする

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

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

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

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

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

アタッチされたデータベースの名前をよりふさわしいHuman-Friendlyの名前に変更する

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

手順 3: Northwind データベースへのポーリング インフラストラクチャの追加

NORTHWND.MDF フォルダーからApp_Data データベースをアタッチしたので、ポーリング インフラストラクチャを追加する準備ができました。 データベースの名前を 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)、新しいストアド プロシージャ、および ProductsCategories、および Suppliers テーブルのトリガーに注意してください。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

INSERT タブと DELETE タブの Drop-Down リストを (なし) に設定する

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

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

最後に、GridView の上に Label Web コントロールを追加し、その ID プロパティを ODSEvents に設定し、その 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 Sub ProductsDataSourceDeclarative_Selecting _
    (sender As Object, e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSourceDeclarative.Selecting
    ODSEvents.Text = "-- Selecting event fired"
End Sub

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

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

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

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

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

databaseName1:tableName1;databaseName2:tableName2;...

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

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

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

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

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

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

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

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

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

手順 6: プログラムによるSqlCacheDependencyClass の操作

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

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

Dim productsTableDependency As _
    New Caching.SqlCacheDependency("NorthwindDB", "Products")

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

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

Dim productsTableDependency As _
    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 クラスの AddCacheItem メソッドには、現在、次のコードが含まれています。

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Make sure MasterCacheKeyArray(0) is in the cache - if not, add it
    If DataCache(MasterCacheKeyArray(0)) Is Nothing Then
        DataCache(MasterCacheKeyArray(0)) = DateTime.Now
    End If
    ' Add a CacheDependency
    Dim dependency As _
        New Caching.CacheDependency(Nothing, MasterCacheKeyArray)
    DataCache.Insert(GetCacheKey(rawKey), value, dependency, _
        DateTime.Now.AddSeconds(CacheDuration), _
        Caching.Cache.NoSlidingExpiration)
End Sub

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

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Add the SqlCacheDependency objects for Products
    Dim productsTableDependency As New _
        Caching.SqlCacheDependency("NorthwindDB", "Products")
    DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, _
        Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub

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

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

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

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

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

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

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

データ ソースの構成ウィザードが完了すると、Visual Studio によって各データ フィールドの BoundFields と CheckBoxFields が 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 テーブルへの変更がチェックされるため、基になるデータが更新されてからキャッシュされたデータが削除されるまでに遅延が発生します。

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

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

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

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

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

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

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Make sure MasterCacheKeyArray(0) is in the cache - if not, add it.
    If DataCache(MasterCacheKeyArray(0)) Is Nothing Then
        DataCache(MasterCacheKeyArray(0)) = DateTime.Now
    End If
    'Create the CacheDependency
    Dim masterCacheKeyDependency As _
        New Caching.CacheDependency(Nothing, MasterCacheKeyArray)
    ' Add the SqlCacheDependency objects for Products, Categories, and Suppliers
    Dim productsTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Products")
    Dim categoriesTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Categories")
    Dim suppliersTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Suppliers")
    ' Create an AggregateCacheDependency
    Dim aggregateDependencies As New Caching.AggregateCacheDependency()
    aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, _
        categoriesTableDependency, suppliersTableDependency)
    DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, _
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub

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

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

概要

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

プログラミングに満足!

もっと読む

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

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。

特別な感謝

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