블로그 이미지
다비도프

만나고, 고백하고, 가슴 떨리고, 설레이고, 웃고, 사랑하고, 키스하고, 함께하고..

Rss feed Tistory
STUDY/SQL Cache Dependency 2007. 4. 30. 11:42

[Study] MSDN - Improved Caching in ASP.NET 2.0

Visual Studio 2005 Technical Articles
Improved Caching in ASP.NET 2.0
 

Stephen Walther
Microsoft Corporation

June 2004

Applies to:
   Microsoft ASP.NET 2.0
   Microsoft ASP.NET framework
   Microsoft SQL Server
   Microsoft Visual Studio .NET

Summary: Stephen Walther looks at the new caching features included in ASP.NET 2.0, and how you can use them to improve the performance and scalability of your ASP.NET applications. (20 printed pages)

Contents

Data Caching Made Easy
Using SQL Cache Invalidation
Using Post-Cache Substitution
Conclusion

The most dramatic way to improve the performance of a database driven Web application is through caching. Retrieving data from a database is one of the slowest operations that you can perform in a Web site. If, however, you can cache the database data in memory, then you can avoid accessing the database with every page request, and dramatically increase the performance of your application.

The one and only drawback to caching is the problem of stale data. If you cache the contents of a database table in memory, and the records in the underlying database table change, then your Web application will display old, inaccurate data. For certain types of data you might not care if the data being displayed is slightly out of date, but for other types of data—such as stock prices and auction bids—displaying data that is even slightly stale is unacceptable.

The initial release of the Microsoft ASP.NET framework did not provide a good solution to this problem. When using the ASP.NET 1.0 framework, you just had to live with this tradeoff between performance and stale data. Fortunately, the Microsoft ASP.NET 2.0 framework includes a new feature called SQL Cache Invalidation that solves this very problem.

In this article, you'll learn about many of the new caching enhancements in the ASP.NET 2.0 framework. First, you'll learn how caching support has been integrated into the new DataSource controls. Next, you'll learn how to configure and take advantage of SQL Cache Invalidation. Finally, we'll take a look at a new control introduced with the ASP.NET 2.0 framework, which enables you to inject dynamic content into a cached page: the Substitution control.

Data Caching Made Easy

One of the biggest changes in the ASP.NET 2.0 framework concerns how you access database data in an ASP.NET page. The ASP.NET 2.0 framework includes a new set of controls, known collectively as the DataSource controls, which enable you to represent a data source such as a database or an XML file.

In the ASP.NET 1.0 framework, you displayed database data with a control by binding the control to either a DataSet or a DataReader. In the ASP.NET 2.0 framework, you'll typically bind a control to a DataSource control instead. By taking advantage of the DataSource controls, you can build ASP.NET pages that display database data without writing any code for accessing the database.

When working with database data, you'll typically use one of the following three DataSource controls:

  • SqlDataSource—Represents any SQL data source, such as a Microsoft SQL Server or an Oracle database.
  • AccessDataSource—A specialized SqlDataSource control designed for working with a Microsoft Access database.
  • ObjectDataSource—Represents a custom business object that acts as a data source.

For example, imagine that you need to display a list of book titles retrieved from a database in a DropDownList control (see Figure 1). The page in Listing 1 illustrates how you can bind a DropDownList control to a SqlDataSource control.

Click here for larger image.

Figure 1. Using the SqlDataSource control to retrieve data

Listing 1. DisplayTitles.aspx

<html>
<head runat="server">
    <title>Display Titles</title>
</head>
<body>
    <form id="form1" runat="server">

        <asp:DropDownList 
            ID="DropDownList1" 
            DataSourceId="SqlDataSource1"
            DataTextField="Title"
            Runat="server" />
     
        <asp:SqlDataSource 
            ID="SqlDataSource1"
            ConnectionString="Server=localhost;database=Pubs"
            SelectCommand="SELECT Title FROM Titles"
            Runat="server" />
    
    </form>
</body>
</html>

Notice that the SqlDataSource control in Listing 1 is used to provide the connection string, and the SQL SELECT command used for retrieving the records from the database. The DropDownList control is bound to the SqlDataSource control through its DataSourceID property.

Caching with the DataSource Controls

The DataSource controls not only enable you to connect more easily to a database, they also make it easier for you to cache database data. Simply by setting a couple of properties on the SqlDataSource control, you can automatically cache the data represented by a DataSource control in memory.

For example, if you want to cache the Titles database table in memory for at least 10 minutes, you can declare a SqlDataSource control like this.

 <asp:SqlDataSource 
   ID="SqlDataSource1"
   EnableCaching="true"
   CacheDuration="600"
   ConnectionString="Server=localhost;database=Pubs"
   SelectCommand="SELECT Title FROM Titles"
   Runat="server" />

When the EnableCaching property has the value true, the SqlDataSource will automatically cache the data retrieved by the SelectCommand. The CacheDuration property enables you to specify, in seconds, how long the data should be cached before it is refreshed from the database.

By default, the SqlDataSource will cache data using an absolute expiration policy—every so many seconds, the data is refreshed from the database. You also have the option of enabling a sliding expiration policy. When the SqlDataSource is configured to use a sliding expiration policy, the data will not be dropped as long as it continues to be accessed. Employing a sliding expiration policy is useful whenever you have a large number of items that need to be cached, since this expiration policy enables you to keep only the most frequently accessed items in memory.

For example, the following SqlDataSourceControl is configured to use a sliding expiration policy of 10 minutes.

        <asp:SqlDataSource 
            ID="SqlDataSource1"
            EnableCaching="true"
            CacheExpirationPolicy="Sliding"
            CacheDuration="600"
            ConnectionString="Server=localhost;database=Pubs"
            SelectCommand="SELECT Title FROM Titles"
            Runat="server" />

Since the CacheExpirationPolicy property is set to the value Sliding and the CacheDuration property is set to the value 600, the data represented by this SqlDataSource will remain in memory just as long as it continues to be accessed within a 10 minute window.

Using SQL Cache Invalidation

SQL Cache Invalidation is one of the most anticipated new features of the ASP.NET 2.0 framework. By taking advantage of SQL Cache Invalidation, you get all of the performance benefits of caching without the problem of stale data. SQL Cache Invalidation enables you to automatically update data in the cache whenever the data changes in the underlying database.

Behind the scenes, SQL Cache Invalidation works by constantly polling the database to check for changes. Every so many milliseconds, the ASP.NET framework checks whether or not there have been any updates to the database. If the ASP.NET framework detects any changes, then any items added to the cache that depend on the database are removed from the cache (they are invalidated).

Note   Microsoft SQL Server 2005 supports a completely different method of SQL Cache Invalidation. You can configure SQL Server 2005 to notify your ASP.NET application whenever changes have been made to a database, a database table, or a database row. This means that the ASP.NET Framework does not need to constantly poll a SQL Server 2005 database for changes.

It is important to understand that SQL Cache Invalidation only works with Microsoft SQL Server version 7 and higher. You cannot use this feature with other databases such as Microsoft Access or Oracle.

You can use SQL Cache Invalidation when caching the output of an entire page, when working with the DataSource controls, or when working directly with the Cache object. We'll examine all three scenarios.

Configuring SQL Cache Invalidation

Before you can take advantage of SQL Cache Invalidation in your Web application, you must first perform some configuration steps. You must configure Microsoft SQL Server to support SQL Cache Invalidation and you must add the necessary configuration information to your application's Web configuration file.

There are two ways that you can configure SQL Server. You can either use the aspnet_regsql command line tool, or you can take advantage of the SqlCacheDependencyAdmin class.

Enabling SQL Cache Invalidation with ASPNET_REQSQL

The aspnet_regsql tool enables you to configure SQL Cache Invalidation from the command line. The aspnet_regsql tool is located in your Windows\Microsoft.NET\Framework\[version] folder. You must use this tool by opening a command prompt and navigating to this folder.

In order to support SQL Cache Invalidation when using the Pubs database, you need to execute the following command.

aspnet_regsql -E -d Pubs -ed

The -E option causes the aspnet_regsql tool to use integrated security when connecting to your database server. The -d option selects the Pubs database. Finally, the -ed option enables the database for SQL Cache Invalidation.

When you execute this command, a new database table named AspNet_SqlCacheTablesForChangeNotification is added to the database. This table contains a list of all of the database tables that are enabled for SQL Cache Invalidation. The command also adds a set of stored procedures to the database.

After you enable a database for SQL Cache Invalidation, you must select the particular tables in the database that you will enable for SQL Cache Invalidation. The following command enables the Titles database table.

aspnet_regsql -E -d Pubs -t Titles -et

The -t option selects a database table. The -et option enables a database table for SQL Cache Invalidation. You can, of course, enable multiple tables by re-executing this command for each database table.

When you execute this command, a trigger is added to the database table. The trigger fires whenever you make a modification to the table and it updates the AspNet_SqlCacheTablesForChangeNotification table.

Finally, if you want to get the list of tables that are currently enabled for SQL Cache Invalidation in a particular database, you can use the following command.

aspnet_regsql -E -d Pubs -lt

This method selects the list of tables from the AspNet_SqlCacheTablesForChangeNotification. Alternatively, you could retrieve this information by performing a query directly against this database table.

Using the SqlCacheDependencyAdmin Class

Behind the scenes, the aspnet_regsql tool uses the methods of the SqlCacheDependencyAdmin class to configure Microsoft SQL Server. If you prefer, you can use the methods of this class directly from within an ASP.NET page.

The SqlCacheDependencyAdmin class has five important methods:

  • DisableNotifications—Disables SQL Cache Invalidation for a particular database.
  • DisableTableForNotifications—Disables SQL Cache Invalidation for a particular table in a database.
  • EnableNotifications—Enables SQL Cache Invalidation for a particular database.
  • EnableTableForNotifications—Enables SQL Cache Invalidation for a particular table in a database.
  • GetTablesEnabledForNotifications—Returns a list of all tables enabled for SQL Cache Invalidation.

For example, the ASP.NET page in Listing 2 enables you to configure SQL Cache Invalidation for any table in the Pubs database (see Figure 2).

Click here for larger image.

Figure 2. Enabling SQL Cache Invalidation from an ASP.NET page

Listing 2. EnableSCI.aspx (C#)

<%@ Page Language="c#" %>
<%@ Import Namespace="System.Web.Caching" %>
<script runat="server">

    const string connectionString = "Server=localhost;Database=Pubs";

    void Page_Load()
    {
        if (!IsPostBack)
        {
            SqlCacheDependencyAdmin.EnableNotifications(
            connectionString);
            SqlDataSource1.SelectParameters.Add("connectionString", 
            connectionString);
        }
    }

    void EnableTable(Object s, EventArgs e)
    {
        try
        {
            SqlCacheDependencyAdmin.EnableTableForNotifications(
              connectionString, txtTableName.Text);
        }
        catch (Exception ex)
        {
            lblErrorMessage.Text = ex.Message;
        }
        txtTableName.Text = "";
    }

</script>

<html>
<head runat="server">
    <title>Enable SQL Cache Invalidation</title>
</head>
<body>
    <form id="form1" runat="server">
    
    <h1>SQL Cache Invalidation</h1>
    
    The following tables are enabled for SQL Cache Invalidation:
 
    <p>
    <asp:GridView id="grdTables" 
      DataSourceID="SqlDataSource1" CellPadding="10" 
      ShowHeader="false" Runat="Server" />
    </p>
    
    <asp:ObjectDataSource 
        ID="SqlDataSource1" 
        TypeName="System.Web.Caching.SqlCacheDependencyAdmin"
        SelectMethod="GetTablesEnabledForNotifications"
        Runat="Server" />
    <p>
    <asp:Label ID="lblErrorMessage" EnableViewState="false" 
      ForeColor="red" Runat="Server" />
    </p>

     <asp:TextBox ID="txtTableName" Runat="Server" /> 
     <asp:Button Text="Enable Table" OnClick="EnableTable" 
       Runat="Server" /> 
 
    </form>
</body>
</html>

Listing 2. EnableSCI.aspx (Visual Basic .NET)

<%@ Page Language="vb" %>
<%@ Import Namespace="System.Web.Caching" %>
<script runat="server">

    Const connectionString As String = "Server=localhost;Database=Pubs"

    Sub Page_Load()
    
        If Not IsPostBack Then
            SqlCacheDependencyAdmin.EnableNotifications( _
            connectionString)
            SqlDataSource1.SelectParameters.Add("connectionString", _
            connectionString)
        End If
    End Sub

    Sub EnableTable(ByVal s As Object, ByVal e As EventArgs)
    
        Try
        
            SqlCacheDependencyAdmin.EnableTableForNotifications( _
              connectionString, txtTableName.Text)
        Catch ex As Exception
            lblErrorMessage.Text = ex.Message
        End Try
        txtTableName.Text = ""
    End Sub

</script>

<html>
<head id="Head1" runat="server">
    <title>ConfigureSCI</title>
</head>
<body>
    <form id="form1" runat="server">
    
    <h1>SQL Cache Invalidation</h1>
    
    The following tables are enabled for SQL Cache Invalidation:
 
    <p>
    <asp:GridView id="grdTables" DataSourceID="SqlDataSource1" 
       CellPadding="10" ShowHeader="false" Runat="Server" />
    </p>
    
    <asp:ObjectDataSource 
        ID="SqlDataSource1" 
        TypeName="System.Web.Caching.SqlCacheDependencyAdmin"
        SelectMethod="GetTablesEnabledForNotifications"
        Runat="Server" />
    <p>
    <asp:Label ID="lblErrorMessage" EnableViewState="false" 
      ForeColor="red" Runat="Server" />
    </p>

     <asp:TextBox ID="txtTableName" Runat="Server" /> 
     <asp:Button ID="Button1" Text="Enable Table" 
      OnClick="EnableTable" Runat="Server" /> 
 
    </form>
</body>
</html>

In Listing 2, the connectionString constant is used to select the database for which SQL Cache Invalidation is enabled (You can change the value of this constant when you want to enable SQL Cache Invalidation for a database other than the Pubs database). Within the Page_Load method, the EnableNotifications method on the SqlCacheDependencyAdmin class is called to enable SQL Cache Invalidation for the database specified by the connectionString constant.

The GridView in Listing 2 displays all of the database tables that are currently enabled for SQL Cache Invalidation. The GridView is bound to an ObjectDataSource control that calls the GetTablesneabledForNotifications method for its SelectMethod.

Finally, you can use the page in Listing 2 to enable additional tables for SQL Cache Invalidation. When you enter the name of a table in the textbox and click the Enable Table button, the EnableTableForNotifications method is called.

Web Configuration Settings for SQL Cache Invalidation

The next step, before you can use SQL Cache Invalidation in your ASP.NET application, is to update your Web configuration file. You need to configure the ASP.NET framework to poll the databases that you have enabled for SQL Cache Invalidation.

The Web configuration file in Listing 3 contains the necessary configuration information to poll the Pubs database.

Listing 3. Web.Config

<configuration>
      
  <connectionStrings>
    <add name="mySqlServer" 
      connectionString="Server=localhost;Database=Pubs" />
  </connectionStrings>
        
  <system.web>

    <caching>
      <sqlCacheDependency enabled="true">
      <databases>
      <add
            name="Pubs"
            connectionStringName="mySqlServer"
            pollTime="60000" />
      </databases>
      </sqlCacheDependency>
    </caching>
    </system.web>
</configuration>

The Web configuration file in Listing 3 contains two sections. The <connectionStrings> section is used to create a database connection string to the Pubs database named mySqlServer.

The caching section is used to configure the SQL Cache Invalidation polling. Within the <databases> subsection, you can list one or more databases that you want to poll for changes. In Listing 3, the database represented by the mySqlServer is polled once a minute (every 60000 milliseconds).

You can specify different polling intervals for different databases. The server must do a little bit of work every time the database is polled for changes. If you don't expect the data in the database to change very often, then you can increase the polling interval.

Using SQL Cache Invalidation with Page Output Caching

Now that we've gotten all of the configuration steps for SQL Cache Invalidation out of the way, we can start taking advantage of it in our ASP.NET pages. One way that you can use SQL Cache Invalidation is with page output caching. Page output caching enables you to cache the entire rendered contents of a page in memory. By taking advantage of SQL Cache Invalidation, you can automatically update the cached page when, and only when, a change is made to a database table.

For example, the page in Listing 4 displays the contents of the Titles database table in a GridView control. At the top of the page, the OutputCache directive is used to cache the contents of the page in memory. The SqlDependency attribute causes the page to be updated whenever the Titles database table changes.

Listing 4. OutputCacheTitles.aspx

<%@ OutputCache SqlDependency="Pubs:Titles" 
    Duration="6000" VaryByParam="none" %>
<html>
<head runat="server">
    <title>Output Cache Titles</title>
</head>
<body>
    <form id="form1" runat="server">
    
    <%= DateTime.Now %>

    <asp:GridView 
      ID="grdTitles" 
      DataSourceID="SqlDataSource1" 
      Runat="Server" />    
    
    <asp:SqlDataSource
      ID="SqlDataSource1"
      SelectCommand="Select * FROM Titles"
      ConnectionString="<%$ ConnectionStrings:mySqlServer %>"
      Runat="Server" />
    
    </form>
</body>
</html>

Notice that the SqlDependency attribute references the name of the database defined within the Web configuration file. Since we specified that the Pubs database should be polled once every minute for changes, if a change is made to the database the page in Listing 4 will be updated within a minute.

You can list more than one database and/or more than one database table for the value of the SqlDependency attribute. To create more than one dependency, simply separate each dependency with a semicolon.

Using SQL Cache Invalidation with the DataSource Control

As an alternative to using SQL Cache Invalidation with page output caching, you can use SQL Cache Invalidation directly with the DataSource controls. You should consider using SQL Cache Invalidation with the DataSource controls when you need to work with the same database data in multiple pages. The SqlDataSource, AccessDataSource, and ObjectDataSource controls all support a SqlCacheDependency property.

For example, the page in Listing 5 uses the SQL Cache Invalidation with the SqlDataSource control.

Listing 5. SqlDataSourceCaching.aspx

<html>
<head id="Head1" runat="server">
    <title>SqlDataSource Caching</title>
</head>
<body>
    <form id="form1" runat="server">

        <%= DateTime.Now %>

        <asp:GridView 
            ID="grdTitles" 
            DataSourceId="SqlDataSource1"
            Runat="server" />
            
        <asp:SqlDataSource 
            ID="SqlDataSource1" 
            EnableCaching="true"
            SqlCacheDependency="Pubs:Titles"
            SelectCommand="select * from titles"
            ConnectionString="<%$ ConnectionStrings:mySqlServer %>"
            Runat="server" />
   
    </form>
</body>
</html>

In Listing 5, the SqlDataSource control is declared with both an EnableCaching attribute and a SqlCacheDependency attribute. The SqlCacheDependency property uses the same syntax as the OutputCache directive's SqlDependency attribute. You list the name of the database, followed by the name of the database table.

Using SQL Cache Invalidation with the Cache Object

A final option is to use SQL Cache Invalidation with the Cache object. This option provides you with the greatest degree of programmatic control over SQL Cache Invalidation.

To use SQL Cache Invalidation with the Cache object, you need to create an instance of the SqlCacheDependency object. You can use the SqlCacheDependency object when inserting a new object into the Cache with the Insert method.

For example, the page in Listing 6 displays the number of records in the Titles database table. The count is cached with a dependency on the underlying database table.

Listing 6. DisplayTitleCount.aspx (C#)

<%@ Page Language="c#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">

    void Page_Load() 
    {
        int count = 0;

        if (Cache["TitleCount"] != null)
        {
            count = (int)Cache["TitleCount"];
        }
        else
        {
            string connectionString = 
              ConfigurationSettings.ConnectionStrings[
              "mySqlServer"].ConnectionString;
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new 
              SqlCommand("SELECT Count(*) FROM Titles", con);
            con.Open();
            count = (int)cmd.ExecuteScalar();
            con.Close();
            Cache.Insert("TitleCount", count, 
              new SqlCacheDependency("Pubs", "Titles"));
        }
        lblTitleCount.Text = count.ToString();
    }

</script>

<html>
<head runat="server">
    <title>Display Title Count</title>
</head>
<body>
    <form id="form1" runat="server">

    <asp:Label ID="lblTitleCount" Runat="Server" />    
    
    </form>
</body>
</html>

Listing 6. DisplayTitleCount.aspx (Visual Basic .NET)

<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">

    Sub Page_Load()
    
        Dim count As Integer = 0

        If Not Cache("TitleCount") Is Nothing Then
            count = Convert.ToInt32(Cache("TitleCount"))
        Else
            Dim connectionString As String = _
              ConfigurationSettings.ConnectionStrings( _
              "mySqlServer").ConnectionString
            Dim con As New SqlConnection(connectionString)
            Dim cmd As New _
              SqlCommand("SELECT Count(*) FROM Titles", con)
            con.Open()
            count = Convert.ToInt32(cmd.ExecuteScalar())
            con.Close()
            Cache.Insert("TitleCount", count, _
              new SqlCacheDependency("Pubs", "Titles"))
        End If
        
        lblTitleCount.Text = count.ToString()
    End Sub

</script>

<html>
<head id="Head1" runat="server">
    <title>Display Titles Count</title>
</head>
<body>
    <form id="form1" runat="server">

    <asp:Label ID="lblTitleCount" Runat="Server" />    
    
    </form>
</body>
</html>

Using Post-Cache Substitution

There are many situations in which you need to cache part of a page, but not the entire page. For example, on the home page of your Web site, you might want to display both a random banner advertisement and the records from a database table. If you cache the entire page, then every user will see the same banner advertisement on each and every page request.

One way to handle this problem of mixing dynamic and cached content is to use Web User Controls. Since you can add an OutputCache directive to a Web User Control, you can cache the contents of a Web User Control even when you do not cache the contents of the containing page.

Sometimes, however, this gets things backwards. You can use Web User Controls to add cached content to a dynamic page. In many situations, however, what you really want to do is add dynamic content to a cached page. For example, imagine that you want to cache the entire contents of a page, except for one little area where you want to display the current user's username. This is a perfect situation in which you'll want to take advantage of post-cache substitution.

The ASP.NET 2.0 framework introduces a new control called the Substitution control. You can use the Substitution control to inject dynamic content into a cached page. The page in Listing 7 uses the Substitution control to inject a username into cached content (see Figure 3).

Figure 3. Displaying a username with the Substitution control

Listing 7. PostCacheSubstitution.aspx (C#)

<%@ Page Language="C#" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>

<script runat="server">

    static string DisplayUsername(HttpContext context) 
    {
        if (!context.Request.IsAuthenticated)
            return "Anonymous";
        else
            return context.User.Identity.Name;
    }

</script>

<html>
<head runat="server">
    <title>Post Cache Substitution</title>
</head>
<body>
    <form id="form1" runat="server">
    
    Welcome <asp:Substitution 
      MethodName="DisplayUsername" Runat="Server" />!
    
    <p>
    This page is cached as you'll notice from the fact that the time
    <%= DateTime.Now.ToString("t") %> never changes.
    </p>
        
    </form>
</body>
</html>

Listing 7. PostCacheSubstitution.aspx (Visual Basic .NET)

<%@ Page Language="vb" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>

<script runat="server">

    Shared Function DisplayUsername(ByVal context As HttpContext) _
      As String
        If Not context.Request.IsAuthenticated Then
            Return "Anonymous"
        Else
            Return context.User.Identity.Name
        End If
    End Function

</script>

<html>
<head id="Head1" runat="server">
    <title>Post Cache Substitution</title>
</head>
<body>
    <form id="form1" runat="server">
    
    Welcome <asp:Substitution 
      ID="Substitution1" 
      MethodName="DisplayUsername" 
      Runat="Server" />!
    
    <p>
    This page is cached as you'll notice from the fact that the time
    <%= DateTime.Now.ToString("t") %> never changes.
    </p>
       
     </form>
</body>
</html>

The Substitution control has one important property: the MethodName property. The MethodName property is used to represent the method that is called to return the dynamic content. The method called by the Substitution control must be a static method (shared in the Visual Basic .NET world). Furthermore, the method must have one parameter that represents the current HttpContext.

In the ASP.NET 2.0 framework, the AdRotator control has been modified to support post-cache substitution. If you add the AdRotator control to a page that uses the OutputCache directive, the AdRotator control is automatically exempt from its containing page's caching policy.

Conclusion

Caching has a dramatic impact on the performance of database-driven Web applications. Fortunately, the ASP.NET 2.0 framework includes a number of significant new enhancements that make it easier to take advantage of caching in your applications.

The new DataSource controls include properties that make it easy to cache database data in memory. By taking advantage of the DataSource controls, you can retrieve database data and cache the data without writing a single line of code.

The new support for SQL Cache Invalidation enables you to automatically reload database data in the cache whenever the data is modified in the underlying database. This feature provides you with all the performance benefits of caching, without the worries of stale data.

Finally, the new Substitution control enables you to more easily mix dynamic content in a cached page. The Substitution control gives you an island of dynamic content in an otherwise cached page.

Related Books


About the author

Stephen Walther wrote the best-selling book on ASP.NET, ASP.NET Unleashed. He was also the architect and lead developer of the ASP.NET Community Starter Kit, a sample ASP.NET application produced by Microsoft. He has provided ASP.NET training to companies across the United States, including NASA and Microsoft, through his company Superexpert (http://www.superexpert.com).

,
STUDY/SQL Cache Dependency 2007. 4. 30. 11:31

[Study] ADO.NET 2.0의 쿼리 알림 (SqlDependency)

ADO.NET 2.0의 쿼리 알림

 

Bob Beauchemin
DevelopMentor

2005년 4월
업데이트한 날짜: 2005년 6월

적용 대상:
   ADO.NET 2.0

요약: ADO.NET 2.0 및 SQL Server 2005에 제공되는 새로운 알림 기술을 사용하여 데이터 변경을 다루는 방법을 배워 보십시오.

목차

소개
캐시 솔루션을 제공하는 SqlDependency
SQL Server 2005의 쿼리 알림
최종 사용자 또는 캐시로 알림 발송
데이터베이스 클라이언트에서 쿼리 알림 사용
SqlDependency 사용
SqlNotificationRequest 사용
ASP.NET에서 SqlCacheDependency 사용
향상된 알림 기능
알림을 사용하지 않는 경우: 교훈
결론

소개

모든 실제 관계형 데이터베이스 응용 프로그램에는 으레 많은 조회 테이블이 들어 있습니다. 그래픽 사용자 인터페이스를 코딩하는 경우 이러한 조회 테이블은 드롭다운 목록 상자를 채우는 목록으로 나타납니다. 필자는 조회 테이블을 읽기 전용 테이블과 읽기 위주(read-mostly) 테이블의 두 가지 유형으로 분류합니다. 이들 간의 차이는 테이블의 변경을 유발하는 부분에 있습니다. 직원 회의나 사용자 회의를 통해 테이블을 변경하는 경우는 읽기 전용 테이블로 볼 수 있으며, 회사의 제품 범주가 포함된 테이블은 이에 대한 좋은 예입니다. 이러한 테이블은 회사에서 신제품을 출시하거나 회사가 재편되지 않는 한 변경되지 않습니다. 읽기 위주 테이블은 비교적 일관되게 유지되지만 최종 사용자가 변경할 수 있는 목록입니다. 이러한 테이블은 대개 드롭다운 목록이 아닌 콤보 상자에 나타납니다. 읽기 위주 테이블의 예로는 호칭(term-of-greeting) 테이블을 들 수 있습니다. 응용 프로그램 디자이너는 대개 Ms., Mr., Mrs. 및 Dr. 같은 일반적인 호칭을 생각할 수 있지만 한 번도 생각해 본 적 없는 호칭을 추가하려는 사용자도 늘 있게 마련입니다. 이러한 일이 얼마나 흔한 경우인지를 보여 주는 예로, 필자가 최근 작업한 중간 크기 제품에는 제3 정규형 관계형 데이터베이스에 350-400개의 테이블이 들어 있었습니다. 이 중 250개 정도가 읽기 전용 또는 읽기 위주 테이블인 것으로 추정됩니다.

3계층 응용 프로그램의 대표적인 예라 할 수 있는 일반적인 웹 응용 프로그램에서는 이러한 유형의 테이블을 가능한 한 많이 캐시하게 됩니다. 이렇게 하면 데이터베이스로의 왕복 횟수는 물론 데이터베이스에서의 쿼리 로드를 줄여 신규 주문 같은 사용 사례에서 응답 성능을 높일 수 있습니다. 읽기 전용 테이블의 경우에는 항상 테이블을 캐시에 유지하고 데이터베이스 관리자(DBA)가 테이블을 다시 로드해야 할 경우에만 이따금 캐시를 다시 로드할 수 있도록 하므로 캐시 작업이 간단합니다. 조직에서 기본 데이터베이스 구조와 콘텐츠를 변경하는 회의를 하는 경우는 아마 거의 없을 것입니다. 중간 계층 캐시에서 읽기 위주 조회 테이블을 새로 고치면 약간의 문제가 발생할 수 있습니다. 일정에 따라 캐시를 간헐적으로 새로 고쳐도 원하는 동작이 만들어지지 않을 뿐 아니라, 사용자는 다른 사용자의 변경 내용을 바로 확인하지 못합니다. 지원 담당자가 다른 응용 프로그램을 사용하여 새 항목을 추가하고 이를 사용하려는 친구에게 인스턴트 메신저 메시지를 보낼 수 있겠지만, 선택한 친구 목록은 새 항목에 포함되지 않습니다. 게다가 다른 사용자가 "누락된 목록 항목"을 다시 추가하려고 하면 항목이 이미 존재한다는 내용의 데이터베이스 오류 메시지가 표시됩니다. 읽기 위주 테이블에 "업데이트 지점"이 둘 이상인 경우에는 대개 이러한 문제로 인해 해당 테이블의 캐시 작업이 이루어지지 않습니다.

예전의 프로그래머들은 메시지 대기열을 사용하는 수작업 솔루션, 파일에 쓰는 트리거 또는 응용 프로그램 외부의 누군가가 읽기 위주 테이블을 업데이트하면 이를 캐시에 알리는 대역 외 프로토콜에 의존했습니다. 이러한 "알림(signaling)" 솔루션은 단순히 행이 추가 또는 변경되었으므로 캐시를 새로 고쳐야 한다는 내용을 캐시에 알리는 역할만 합니다. 특정 행이 변경 또는 추가되었음을 캐시에 알리는 것은 조금 다른 문제이며, 분산 데이터베이스 및 트랜잭션 또는 병합 복제 영역에 해당됩니다. 오버헤드가 낮은 알림 솔루션에서는 프로그램에 "잘못된 캐시"라는 메시지가 표시되면 전체 캐시를 새로 고치기만 할 뿐입니다.

캐시 솔루션을 제공하는 SqlDependency

SQL Server 2005 및 ADO.NET 2.0 사용자는 이제 SqlClient 데이터 공급자와 쿼리 알림이라는 데이터베이스에 기본 제공되는 알림 솔루션을 사용할 수 있습니다. 이는 이러한 일상적인 문제를 해결하는 사용이 편리한 최초의 기본 제공 솔루션입니다. 쿼리 알림은 ASP.NET 2.0의 기본 기능에서도 직접 지원됩니다. ASP.NET 캐시는 알림에 등록할 수 있으며 이 알림은 AST.NET에서 사용되는 페이지 및 페이지 조각 캐시와도 함께 사용할 수 있습니다.

이처럼 유용한 기능을 수행하는 인프라는 SQL Server 2005 쿼리 엔진, SQL Server Service Broker, 시스템 저장 프로시저인 sp_DispatcherProc, ADO.NET SqlNotification(System.Data.Sql.SqlNotificationRequest), SqlDependency(System.Data.SqlClient.SqlDependency) 클래스 및 ASP.NET Cache(System.Web.Caching.Cache) 클래스로 구성되어 있습니다. 요컨대 이 인프라는 다음과 같이 작동합니다.

  1. 각 ADO.NET SqlCommand에는 알림 요청을 나타내는 Notification 속성이 들어 있습니다. SqlCommand가 실행될 때 Notification 속성이 있으면 알림 요청을 나타내는 네트워크 프로토콜(TDS)이 요청에 추가됩니다.
  2. SQL Server는 쿼리 알림 인프라를 사용하여 요청된 알림에 대해 구독을 등록하고 명령을 실행합니다.
  3. SQL Server는 SQL DML 문에서 처음에 반환된 행 집합을 변경시킬 수 있는 부분이 있는지 "감시"합니다. 변경이 발생하면 Service Broker 서비스로 메시지가 전송됩니다.
  4. 이 메시지는 다음과 같은 작업을 수행합니다.
    1. 등록된 클라이언트에 다시 알림이 발생하도록 합니다.
    2. 고급 클라이언트가 사용자 지정 처리를 할 수 있도록 Service Broker 서비스 대기열에 그대로 유지됩니다.

그림 1. 쿼리 알림의 상위 개요

ASP.NET SqlCacheDependency(System.Web.Caching.SqlCacheDependency) 클래스 및 OutputCache 지시문은 SqlDependency를 통해 자동 알림 기능을 사용합니다. 더 많은 컨트롤이 필요한 ADO.NET 클라이언트는 SqlNotificationRequest를 사용하고 Service Broker 대기열을 수동으로 처리하여 필요한 사용자 지정 구문을 모두 구현할 수 있습니다. Service Broker에 대한 자세한 설명은 이 기사에서는 다루지 않지만 샘플 서적의 A First Look at SQL Server 2005 for Developers(영문) 부분과 Roger Wolter가 저술한 "A First Look at SQL Server 2005 Service Broker"(영문) 기사를 참조하면 이를 이해하는 데 많은 도움이 될 것입니다.

계속하기 전에, 행 집합이 변경되면 SqlNotificationRequest 또는 SqlDependency에 각각 알림 메시지가 하나씩 전달되는 부분을 확실히 이해하고 넘어가야 합니다. 이 메시지는 변경을 유발하는 부분이 데이터베이스 INSERT 문이든, 하나 이상의 행을 삭제하는 DELETE 문이든, 하나 이상의 행을 업데이트하는 UPDATE 문이든 관계없이 동일합니다. 알림에 변경된 특정 행 또는 변경된 수와 관련된 정보는 들어 있지 않습니다. 캐시 개체나 사용자의 응용 프로그램에서 이러한 단일 변경 메시지를 받으면 전체 행 집합을 새로 고치고 알림에 다시 등록하는 방법 밖에는 없습니다. 여러 개의 메시지를 수신하지는 않으며 하나의 메시지가 발생한 후에는 데이터베이스의 사용자 구독이 종료됩니다. 또한 쿼리 알림 프레임워크는 이벤트에 대한 알림은 많이 받을수록 좋다는 것을 전제로 하여 작동합니다. 알림은 행 집합이 변경될 때는 물론 행 집합에 참여한 테이블이 삭제 또는 변경될 때, 데이터베이스를 재활용할 때 등과 같은 경우에도 전송됩니다. 캐시 또는 프로그램의 응답은 캐시된 데이터를 새로 고치고 알림에 다시 등록하는 것과 관계없이 대개 동일합니다.

지금까지 관련된 일반적인 구문에 대해 알아보았으므로 이제는 다음과 같은 세 가지 관점에서 작동 원리를 자세히 살펴보겠습니다.

  1. SQL Server의 쿼리 알림 구현 방식 및 옵션 발송자의 작동 원리
  2. SqlClientSqlDependencySqlNotificationRequest가 클라이언트/중간 계층에서 작동하는 방식
  3. ASP.NET 2.0의 SqlDependency 지원 방식

SQL Server 2005의 쿼리 알림

SQL Server는 서버 수준에서 클라이언트의 쿼리를 일괄적으로 처리합니다. 각 쿼리(여기서 쿼리는 SqlCommand.CommandText 속성임)에는 일괄 처리가 하나만 포함될 수 있지만, 일괄 처리에는 여러 개의 T-SQL 문이 들어 있을 수 있습니다. SqlCommand는 여러 개의 T-SQL 문이 포함될 수 있는 저장 프로시저나 사용자 정의 함수를 실행하는 데도 사용할 수 있습니다. 또한 SQL Server 2005에서는 클라이언트의 쿼리에 알림을 배달하는 Service Broker 서비스 이름, 알림 ID(문자열) 및 알림 시간 제한과 같은 세 가지 추가 정보가 포함될 수 있습니다. 쿼리 요청에 이러한 세 가지 정보가 나타나 있고 요청에 SELECT 또는 EXECUTE 문이 들어 있으면 SQL Server는 해당 쿼리에 의해 만들어진 모든 행 집합에 다른 SQL Server 세션의 변경 내용이 있는지 "감시"하게 됩니다. 저장 프로시저를 실행할 때처럼 여러 개의 행 집합이 생성되는 경우 SQL Server는 모든 행 집합을 "감시"합니다.

그렇다면 여기서 행 집합을 "감시"한다는 것은 어떤 의미이며 SQL Server는 이를 어떤 식으로 수행할까요? 행 집합의 변경 내용을 탐지하는 일은 SQL Server 엔진에서 수행하는 작업의 일부이며, 인덱싱된 뷰의 동기화를 위한 SQL Server 2000의 변경 탐지 기능부터 적용된 메커니즘이 사용됩니다. Microsoft는 SQL Server 2000부터 인덱싱된 뷰 개념을 도입했습니다. SQL Server의 뷰는 하나 이상의 테이블에 있는 열에 대한 쿼리로 구성되어 있습니다. 뷰 이름은 테이블 이름처럼 사용할 수 있습니다. 예를 들면 다음과 같습니다.

CREATE VIEW WestCoastAuthors
AS
SELECT * FROM authors
  WHERE state IN ('CA', 'WA', 'OR')

이제 뷰를 다음과 같이 쿼리의 테이블처럼 사용할 수 있습니다.

SELECT au_id, au_lname FROM WestCoastAuthors
  WHERE au_lname LIKE 'S%'

대부분의 프로그래머에게 뷰는 익숙하지만 인덱싱된 뷰는 생소할 수 있습니다. 인덱싱되지 않은 뷰의 뷰 데이터는 데이터베이스에 개별 복사본으로 저장되지 않으므로 뷰를 사용할 때마다 기본 제공 쿼리가 실행됩니다. 따라서 위의 예에서는 행 집합 WestCoastAuthors를 가져오는 쿼리가 실행되며 이 쿼리에는 필요한 특정 WestCoastAuthors를 끌어오는 술어가 포함됩니다. 인덱싱된 뷰는 데이터의 복사본을 저장하므로 WestCoastAuthors를 인덱싱된 뷰로 만들면 이들 작성자의 데이터 복사본을 두 개씩 얻게 됩니다. 이제 인덱싱된 뷰 또는 원본 테이블 중 하나를 경로로 선택하여 데이터를 업데이트할 수 있습니다. 그에 따라 SQL Server는 두 물리적 데이터 저장소에서 모두 변경 내용을 탐지한 후 다른 데이터 저장소에 이를 적용해야 합니다. 이러한 변경 탐지 메커니즘은 쿼리 알림이 설정되어 있을 때 엔진에서 사용하는 메커니즘과 동일합니다.

변경 탐지 특유의 구현 방식으로 인해 모든 뷰를 인덱싱할 수는 없습니다. 인덱싱된 뷰에 적용되는 이러한 제한 사항은 쿼리 알림에 사용되는 쿼리에도 적용됩니다. 예를 들어 WestCoastAuthors 뷰의 경우 뷰가 작성된 방식으로 인덱싱할 수는 없습니다. 이 뷰를 인덱싱하려면 뷰 정의에 두 부분으로 구성된 이름을 사용하고 모든 행 집합 열 이름을 명시적으로 지정해야 합니다. 그러면 이제 뷰를 변경하여 인덱싱을 수행해 보겠습니다.

CREATE VIEW WestCoastAuthors
WITH SCHEMABINDING
AS
SELECT au_id, au_lname, au_fname, address, city, state, zip, phone
  FROM dbo.authors
  WHERE state in ('CA', 'WA', 'OR')

여기서 인덱싱된 뷰 규칙을 따르는 쿼리만 알림과 함께 사용할 수 있습니다. 쿼리 결과의 변경 여부를 확인하는 데도 동일한 메커니즘이 사용되지만 인덱싱된 뷰에서처럼 쿼리 알림으로 인해 SQL Server에 데이터의 복사본이 생성되지는 않습니다. 인덱싱된 뷰에 대한 규칙 목록은 상당히 방대하며 SQL Server 2005 온라인 설명서에서 확인할 수 있습니다. 쿼리가 알림 요청과 함께 전송되고 규칙을 따르지 않는 경우 SQL Server는 즉시 "유효하지 않은 쿼리입니다"라는 알림을 이유와 함께 게시합니다. 그렇다면 알림은 "어디에 게시"될까요?

SQL Server 2005에서는 Service Broker 기능을 사용하여 알림을 게시합니다. Service Broker는 SQL Server에 기본 제공되는 비동기 대기열 기능이며, 쿼리 알림은 이 Service Broker 서비스를 사용합니다. 이 경우 서비스는 비동기 메시지의 대상이며 메시지는 계약이라는 일련의 특정 규칙을 따라야 합니다. Service Broker 서비스는 항상 물리적 메시지 대상인 대기열과 관련되어 있습니다. 쿼리 알림에 대한 계약은 SQL Server에 기본적으로 제공되며 이름은 http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification입니다.

참고   SQL Server의 계약 개체 이름은 URL이지만 위치를 나타내지 않습니다. 다시 말해 테이블 이름인 dbo.authors처럼 개체 이름에 불과합니다.

여기까지 종합해 보면 쿼리 알림 메시지의 대상은 적절한 계약을 지원하는 서비스라는 점을 알 수 있습니다. 서비스 등을 정의하는 SQL DDL은 다음과 같습니다.

CREATE QUEUE mynotificationqueue
CREATE SERVICE myservice ON QUEUE mynotificationqueue
 ([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification])
GO

이제 쿼리 알림 요청에서 myservice 서비스를 대상으로 사용할 수 있을 것입니다. SQL Server는 서비스에 메시지를 보내는 방식으로 알림을 전송합니다. 고유한 서비스를 사용하거나 SQL Server에서 MSDB 데이터베이스의 기본 제공 서비스를 사용하도록 할 수도 있습니다. 고유한 서비스를 사용하는 경우에는 메시지를 읽고 처리하는 코드를 작성해야 합니다. 하지만 MSDB에 기본 제공되는 서비스를 사용하는 경우에는 메시지를 배달하는 미리 작성된 코드가 제공됩니다. 이 부분에 대해서는 나중에 다시 설명하겠습니다.

쿼리 알림에서 Service Broker를 사용하기 때문에 몇 가지 추가 요구 사항이 적용됩니다.

  1. Service Broker는 쿼리 알림을 실행하는 데이터베이스에서 활성화해야 합니다. 베타 2의 경우 AdventureWorks 샘플 데이터베이스에는 Service Broker가 기본적으로 활성화되어 있지 않지만, "ALTER DATABASE SET ENABLE_BROKER" DDL 문을 사용하여 활성화할 수 있습니다.
  2. 쿼리를 전송하는 사용자는 쿼리 알림을 구독할 수 있는 권한을 가지고 있어야 합니다. 쿼리 알림 구독은 데이터베이스마다 수행합니다. 다음 DDL을 실행하면 사용자에게 현재 데이터베이스에서 구독을 수행할 수 있는 'bob' 권한이 부여됩니다.
          GRANT SUBSCRIBE QUERY NOTIFICATIONS TO bob
    

최종 사용자 또는 캐시로 알림 발송

지금까지 알림 요청과 함께 올바른 쿼리 일괄 처리를 SQL Server로 전송했습니다. SQL Server는 행 집합을 감시하며 누군가 행 집합을 변경하면 선택한 서비스로 메시지가 전송됩니다. 그렇다면 이제 무엇을 해야 할까요? 먼저, 메시지를 읽고 알림이 발생할 때 필요한 모든 로직을 수행하는 사용자 지정 코드를 작성합니다. 아니면 기본 제공되는 발송자에서 이를 자동으로 수행하도록 할 수도 있습니다. 그러면 발송자에 대해 한번 살펴보겠습니다.

사용자 지정 서비스를 지정하지 않으면 쿼리 알림은 http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService라는 MSDB 데이터베이스의 기본 서비스를 사용합니다.서비스 대기열에 메시지가 도착하면 sp_DispatcherProc이라는 대기열과 관련된 저장 프로시저에서 메시지를 자동으로 처리합니다. 한 가지 흥미로운 점은 이 프로시저는 .NET으로 작성된 코드를 사용한다는 점으로, 쿼리 알림을 자동으로 배달하려면 SQL Server 2005 인스턴스에 .NET CLR(공용 언어 런타임)을 로드하는 기능이 활성화되어 있어야 합니다. .NET CLR의 로드는 SQL Server 인스턴스별로 활성화하거나 비활성화할 수 있습니다.

쿼리 알림 메시지가 도착하면 sp_DispatcherProc(이하 "발송자")은 SqlDependency 알림 대기열의 쿼리 알림 구독 목록을 검색하여 각 구독자에게 메시지를 보냅니다. 서버는 발송자를 사용하여 데이터가 변경된 사실을 클라이언트에 알립니다. 이렇게 되면 클라이언트가 알림에 대해 폴링할 필요가 없을 뿐 아니라 알림을 수신하기 위해 SQL Server에 대한 연결을 열어 놓을 필요가 없기 때문에 매우 유용합니다. 발송자는 HTTP 프로토콜/TCP 및 개인 프로토콜을 사용하여 이 알림을 각 구독자에게 보냅니다. 서버와 클라이언트 간 통신은 선택적으로 인증할 수 있습니다. 알림이 배달되고 나면 활성 구독 목록에서 구독이 삭제됩니다. 클라이언트 구독별로 알림을 하나씩만 받으므로 쿼리를 다시 전송하고 다시 구독하는 일은 클라이언트에서 담당합니다.

데이터베이스 클라이언트에서 쿼리 알림 사용

지금까지 내부 작업을 모두 살펴보았으니 이제 이를 사용하는 ADO.NET 클라이언트를 작성해 보겠습니다. 어째서 비교적 간단한 클라이언트측 코드를 작성하면서 이처럼 많은 설명이 필요했을까요? 코드는 상당히 쉽게 작성할 수 있지만 반드시 규칙을 따라야만 합니다. 가장 일반적으로 발생하는 문제는 알림에 대해 유효하지 않은 쿼리를 전송한 다음 Service Broker와 사용자 권한을 설정하는 과정을 잊어버리는 경우입니다. 이로 인해 이처럼 강력한 기능이 애물단지로 전락하게 되었을 뿐 아니라, 일부 베타 테스터는 기능이 제대로 작동하지 않는다는 생각까지 하게 되었습니다. 따라서 간단한 예비 작업과 조사를 수행하면 많은 도움이 됩니다. 마지막으로 발송자에 대해 Service Broker 서비스 같은 속성과 프로토콜을 지정하게 되므로 먼저 내부 작업을 수행하는 편이 유익합니다. 이제는 이러한 용어들이 어떤 의미인지 알게 되었을 것입니다.

ADO.NET에서 쿼리 알림 클라이언트를 작성할 수 있는 것처럼 여기서는 OLE DB나 새로운 HTTP 웹 서비스 클라이언트를 사용하여 이를 작성할 예정입니다. 하지만 유의해야 할 부분은 쿼리 알림은 클라이언트측 코드를 통해서만 사용할 수 있는 점입니다. 이 기능을 T-SQL과 바로 함께 사용하거나 SqlServer 데이터 공급자를 사용하여 SQL Server와 통신하는 SQLCLR 프로시저 코드와 함께 사용할 수는 없습니다.

System.Data.dll 어셈블리에는 SqlDependencySqlNotificationRequest라는 두 개의 클래스가 들어 있습니다. SqlDependency는 발송자를 사용하여 자동 알림을 실행하려는 경우 사용합니다. SqlNotificationRequest는 알림 메시지를 직접 처리하려는 경우에 사용합니다. 그럼, 지금부터 이들 클래스의 예제를 살펴보겠습니다.

SqlDependency 사용

SqlDependency의 사용 단계는 간단합니다. 먼저, 쿼리 알림을 수행해야 하는 SQL 문이 들어 있는 SqlCommand를 만들고, SqlCommandSqlDependency를 연결합니다. 그런 다음 SqlDependency OnChanged 이벤트에 대해 이벤트 처리기를 등록합니다. 그런 다음 SqlCommand를 실행합니다. DataReader를 처리하고 닫는 것은 물론 연관된 SqlConnection까지 닫을 수 있습니다. 이제 행 집합이 변경되면 발송자를 통해 알림을 수신하게 됩니다. 이 개체에 대한 이벤트는 서로 다른 스레드에서 발생합니다. 따라서 코드 실행 중에 이벤트가 발생하는 상황을 처리할 수 있도록 대비해야 합니다. 경우에 따라 이벤트가 발생하는 상황에 일괄 처리 결과를 계속 처리할 수도 있습니다. 코드는 다음과 같습니다.

using System;
using System.Data;
using System.Data.SqlClient;
static void Main(string[] args)
{
  string connstring = GetConnectionStringFromConfig();
  using (SqlConnection conn = new SqlConnection(connstring))
  using (SqlCommand cmd = 
   // 2-part table names, no "SELECT * FROM ..."
   new SqlCommand("SELECT au_id, au_lname FROM dbo.authors", conn))
 {
  try
  {
    // cmd와 연관된 종속성을 만듭니다.
    SqlDependency depend = new SqlDependency(cmd);
    // 처리기를 등록합니다.
    depend.OnChanged += new OnChangedEventHandler(MyOnChanged);
    conn.Open();
    SqlDataReader rdr = cmd.ExecuteReader();
    // DataReader를 처리합니다.
    while (rdr.Read())
    Console.WriteLine(rdr[0]);
    rdr.Close();
    // 무효화가 완료될 때까지 기다립니다.
    Console.WriteLine("Press Enter to continue");
    Console.ReadLine();
  }
  catch (Exception e)
   { Console.WriteLine(e.Message); }
 }
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
  Console.WriteLine("result has changed");
  Console.WriteLine("Source " + e.Source);
  Console.WriteLine("Type " + e.Type);
  Console.WriteLine("Info " + e.Info);(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)
}

Visual Basic .NET에서 친숙한 WithEvents 키워드와 SqlDependency를 함께 사용해도 이와 동일한 코드를 작성할 수 있습니다. 이 프로그램은 기본 결과의 변경 횟수에 관계없이 OnChanged 이벤트를 한 번만 가져와 처리합니다. 실제 사용 시 알림을 받았을 때 해야 할 일은 새로운 알림과 함께 명령을 다시 전송하고 그 결과를 바탕으로 캐시를 새 데이터로 새로 고치는 것입니다. 위 예제의 Main()에 여기서 작성한 코드를 가져와 명명된 루틴으로 이동하면 코드의 형태는 다음과 같습니다.

static void Main(string[] args)
{
    GetAndProcessData(); 
    UpdateCache();
    // 사용자가 프로그램을 종료할 때까지 기다립니다.
    Console.WriteLine("Press Enter to continue");
    Console.ReadLine();
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
    GetAndProcessData(); 
    UpdateCache();
}

일부 단락을 보면 이것이 ASP.NET Cache 클래스를 데이터 캐시로 사용하여 ASP.NET 2.0를 실행하는 것과 완전히 똑같다는 사실을 알 수 있습니다.

SqlDependency를 사용하는 경우에는 SQL Server 2005의 발송자 구성 요소를 통해 클라이언트에 연결하고 알림 메시지를 보냅니다. 이는 SqlConnection을 사용하지 않는 대역 외 통신입니다. 이는 또한 클라이언트가 SQL Server를 통해 "네트워크를 사용할 수 있어야"하며 방화벽 및 네트워크 주소 변환으로 인해 문제가 발생할 수 있다는 것을 의미합니다. 향후 베타 버전이 출시되면 포트 구성에 대한 제어 성능이 강화되어 방화벽을 보다 자유롭게 사용할 수 있을 것입니다. SqlDependency 생성자에 매개 변수를 지정하여 서버와 클라이언트가 통신하는 방식을 완벽하게 구성할 수 있습니다. 다음은 이에 대한 예제입니다.

SqlDependency depend = new SqlDependency(cmd,
    null,
    SqlNotificationAuthType.None,
    SqlNotificationEncryptionType.None,
    SqlNotificationTransports.Tcp,
    10000);

SqlDependency 생성자를 사용하면 기본값 대신 다양한 동작을 선택할 수 있습니다. 변경할 동작 중 가장 유용한 것은 서버에서 클라이언트와 연결하는 데 사용하는 유선 전송입니다. 이 예제에서는 SqlNotificationTransports.Tcp를 사용하므로 서버에서는 TCP 또는 HTTP를 사용할 수 있습니다. 이 매개 변수의 기본값은 SqlNotificationTransports.Any이며, 서버에서는 이를 통해 사용할 전송을 "결정"할 수 있습니다. Any가 지정된 경우 서버는 클라이언트 운영 체제에 커널 모드 HTTP 지원이 포함된 경우에는 HTTP를 선택하고, 그렇지 않은 경우에는 TCP를 선택하게 됩니다. Windows Server 2003 및 Windows XP(SP2)에는 커널 모드 HTTP 지원이 포함되어 있습니다. 또한 네트워크를 통해 메시지를 보내기 때문에 사용할 인증 형식을 지정할 수 있습니다. EncryptionType은 현재 매개 변수로 사용되고 있지만 이후 베타 버전에서는 제거될 예정입니다. 현재 두 값 모두에 대한 기본값은 None입니다. SqlNotificationAuthType은 통합 인증도 지원합니다. 또한 구독에 대한 시간 제한 값과 SQL Server Service Broker 서비스의 이름을 명시적으로 지정할 수 있습니다. 서비스 이름은 예제에서처럼 대개 null로 설정되지만 기본 제공 서비스인 SqlQueryNotificationService를 명시적으로 지정할 수도 있습니다. 대개 다시 정의할 확률이 가장 높은 매개 변수는 SqlNotificationTransport와 시간 제한입니다. 이러한 매개 변수는 서버측 발송자의 동작을 지정하므로 SqlDependency에만 적용할 수 있습니다. SqlNotificationRequest를 사용하는 경우에는 발송자를 사용하지 않습니다.

SqlNotificationRequest 사용

SqlNotificationRequest를 사용하려면 SqlDependency보다 약간 복잡한 설정 과정을 거쳐야 하지만 메시지 처리는 프로그램에서 담당하게 됩니다. SqlDependency를 사용하면 서버의 알림이 MSDB의 SqlQueryNotificationService로 전송되어 메시지가 자동으로 처리됩니다. 하지만 SqlNotificationRequest를 사용하는 경우에는 메시지를 직접 처리해야 합니다. 다음은 SqlNotificationRequest와 이 기사의 앞부분에서 정의한 서비스를 사용하는 간단한 예제입니다.

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
class Class1
{
  string connstring = null;
  SqlConnection conn = null;
  SqlDataReader rdr = null;
  static void Main(string[] args)
  {
    connstring = GetConnectionStringFromConfig();
    conn = new SqlConnection(connstring));
    Class1 c = new Class1();
    c.DoWork();  
  }
  void DoWork()
  {
    conn.Open();
    rdr = GetJobs(2);
    if (rdr != null)
    {
      rdr.Close();
      WaitForChanges();
    }
    conn.Dispose();
  }
  public SqlDataReader GetJobs(int JobId)
  {
    using (SqlCommand cmd = new SqlCommand(
        "Select job_id, job_desc from dbo. jobs where job_id = @id",
        conn))
    {
      
    try
    {
      cmd.Parameters.AddWithValue("@id", JobId);
      SqlNotificationRequest not = new SqlNotificationRequest();
      not.Id = new Guid();
      // 이것은 notificationqueue라는 대기열과 연관된 pubs 데이터베이스의
      // MyService라는 서비스여야 합니다(아래 참조).
      // 서비스는 QueryNotifications 계약에 따라야 합니다.
      not.Service = "myservice";
      not.Timeout = 0; 
      // 알림 요청을 연결합니다.
      cmd.Notification = not;
      rdr = cmd.ExecuteReader();
      while (rdr.Read())
     Console.WriteLine(rdr[0]);
     rdr.Close();
    }
    catch (Exception ex)
    { Console.WriteLine(ex.Message); }
    return rdr;
    }
  }
  public void WaitForChanges()
  {
    // 알림이 대기열에 나타날 때까지
    // 기다렸다가 직접 읽고 확인합니다.
    using (SqlCommand cmd = new SqlCommand(
     "WAITFOR (Receive convert(xml,message_body) from notificationqueue)",    
      conn))
    {
      object o = cmd.ExecuteScalar();
      // 필요에 따라 알림 메시지를 처리합니다.
      Console.WriteLine(o); 
    }
  }

SqlNotificationRequest를 사용하는 장점(추가 작업이기도 함)은 알림을 기다렸다가 직접 처리해야 한다는 점입니다. SqlDependency를 사용하는 경우에는 알림을 수신하기 전까지 데이터베이스에 다시 연결할 필요가 없습니다. SqlNotificationRequest 알림은 기다릴 필요가 없으며 대기열만 가끔씩 폴링하면 됩니다. SqlNotificationRequest의 또 다른 사용 예로는 알림이 발생하면 실행조차 되지 않은 특수한 응용 프로그램을 작성하는 경우입니다. 응용 프로그램이 시작되면 대기열에 연결되어 이전 응용 프로그램 실행의 "영구 캐시" 결과가 더 이상 유효하지 않음을 확인할 수 있습니다.

알림을 몇 시간 또는 몇 일 동안 기다릴 수 있는 응용 프로그램에 대해 설명하다 보면, "데이터가 변경되지 않으면 알림은 언제 사라집니까?"와 같은 질문을 받게 됩니다. 데이터베이스의 구독 테이블에서 삭제되는 것처럼 알림을 사라지게 하는 유일한 요인은 알림이 발생되거나 알림이 만료되는 경우뿐입니다. 알림 구독이 SQL 리소스를 사용하고 쿼리 및 업데이트에 오버헤드를 추가하기 때문에 이를 기다리는 일이 귀찮은 데이터베이스 관리자는 SQL Server에서 알림을 수동으로 삭제할 수도 있습니다. 먼저 SQL Server 2005 동적 뷰에 대해 알림을 쿼리하고 성가신 알림 구독을 검색한 다음 명령을 실행하여 제거하면 됩니다.

  -- 모든 구독을 찾습니다.
  SELECT * FROM sys.dm_qn_subscriptions
  -- 원하는 구독의 ID를 선택한 다음
  -- ID = 42인 구독을 삭제합니다.
  KILL QUERY NOTIFICATION SUBSCRIPTION 42

ASP.NET에서 SqlCacheDependency 사용

ASP.NET Cache 클래스에도 알림을 연결할 수 있습니다. ASP.NET 2.0에서는 CacheDependency 클래스를 하위 클래스로 지정할 수 있으며, SqlCacheDependencySqlDependency를 캡슐화하고 다른 ASP.NET CacheDependency와 마찬가지로 작동합니다. SqlCacheDependency는 사용하는 버전이 SQL Server 2005이든, 이전 버전의 SQL Server든 관계없이 작동하므로 SqlDependency보다 효율성이 뛰어납니다. 물론 SQL Server 2005 이전 버전에서는 완전히 다른 방식으로 구현되어 있습니다.

이전 버전의 SQL Server를 사용하는 경우에는 "감시"할 테이블의 트리거를 사용하는 방식으로 SqlCacheDependency가 작동하게 됩니다. 이러한 트리거는 다른 SQL Server 테이블에 행을 쓰는 역할을 하며, 그러면 이 테이블이 폴링됩니다. 어떤 테이블의 종속성 및 폴링 간격 값을 활성화할지도 구성할 수 있습니다. SQL Server 2005 이전 구현에 대한 자세한 설명은 이 기사의 범위를 벗어나므로 자세한 내용은 Improved Caching in ASP.NET 2.0(영문)을 참조하십시오.

SQL Server 2005를 사용하는 경우 위의 ADO.NET 예제와 유사하게 SqlCacheDependency에서 SqlDependency 인스턴스를 캡슐화합니다. 다음은 SqlCacheDependency를 사용하는 간단한 코드 예제입니다.

// Page.Load를 호출했습니다.
CreateSqlCacheDependency(SqlCommand cmd)
{
  SqlCacheDependency dep = new SqlCacheDepedency(cmd);
  Response.Cache.SetExpires(DateTime.Now.AddSeconds(60);
  Response.Cache.SetCacheability(HttpCacheability.Public);
  Response.Cache.SetValidUntilExpires(true);
  Response.AddCacheDependency(dep);
}

사용이 편리한 뛰어난 기능 한 가지는 SqlCacheDependency가 페이지 또는 페이지 조각 캐시에도 연결된다는 점입니다. 또한 특정 ASP.NET OutputCache 지시문에서 모든 SqlCommands를 선언적으로 활성화할 수 있습니다. 여기에서는 페이지의 모든 SqlCommands에 대해 동일한 SqlDependency가 사용되며, SQL Server 2005 데이터베이스에 사용되는 형태와 유사합니다.

<%OutputCache SqlDependency="CommandNotification" ... %>

CommandNotification은 "SQL Server 2005 및 SqlDependency를 사용한다"는 의미의 키워드 값입니다. 이전 버전의 SQL Server를 사용하는 경우에는 이 지시문 매개 변수의 구문이 완전히 다릅니다. 또한 특정 운영 체제 버전에서 ASP.NET 2.0을 실행하면 CommandNotification 키워드 값만 활성화됩니다.

향상된 알림 기능

SQL Server 쿼리 알림의 디자인 정책은 클라이언트에 대해 알림을 빠뜨리는 것보다는 지나칠 정도로 자주 보내는 게 낫다는 것을 골자로 하고 있습니다. 대개 다른 누군가 행을 변경하여 캐시가 무효화되면 알림을 받게 되지만 항상 그런 것은 아닙니다. 예를 들어 DBA가 데이터베이스를 재활용하면 알림을 받게 됩니다. 또한 쿼리의 테이블 중 하나라도 변경되거나 삭제되거나 잘려도 알림을 받습니다. 쿼리 알림은 SQL Server 리소스를 소비하므로 SQL Server 리소스의 스트레스가 심각해지면 내부 테이블에서 쿼리 알림을 제거하기 시작할 수 있으며 이런 경우에도 클라이언트에서 알림을 받게 됩니다. 또한 각 알림 요청에는 시간 제한 값이 들어 있으므로 구독 제한 시간이 초과되면 알림을 수신합니다.

SqlDependency를 사용하는 경우 발송자는 SqlNotificationEventArgs 인스턴스에 이 정보를 요약합니다. 이 클래스에는 Info, Source 및 Type의 세 가지 속성이 포함되어 있으며, 이를 통해 알림을 유발하는 부분을 지정할 수 있습니다. SqlNotificationRequest를 사용하는 경우 대기열 메시지의 message_body 필드에 이와 동일한 정보가 포함된 XML 문서가 있지만 XPath 또는 XQuery를 사용하여 직접 구문 분석해야 합니다. 다음은 앞의 ADO.NET SqlNotificationRequest 예제에서 만든 샘플 XML 문서입니다.

<qn:QueryNotification 
 xmlns:qn="http://schemas.microsoft.com/SQL/Notifications/QueryNotification" 
 id="2" type="change" source="data" info="update" 
 database_id="6" user_id="1">
<qn:Message>{CFD53DDB-A633-4490-95A8-8E837D771707}</qn:Message>
</qn:QueryNotification>

필자는 job_id = 5인 행에서 job_desc 열 값을 "new job"으로 변경하여 이 알림을 만들었지만 message_body에는 이 정보가 표시되지 않습니다. 이를 통해 알림 프로세스의 마지막 몇 가지 차이점을 확인할 수 있습니다. 알림의 기능은 SQL 문을 통해 어떤 항목이 변경되어 행 집합이 변경될 수 있음을 인식하는 정도에 그친다는 사실입니다. 알림 기능으로는 UPDATE 문이 행의 실제 값을 변경하지 않는 경우까지는 인식하지 못합니다. 예를 들어, 행을 job_desc = "new job"에서 job_desc = "new job"으로 변경하면 알림이 생성됩니다. 또한 쿼리 알림은 비동기적으로 수행되며 명령 또는 일괄 처리를 실행할 때 등록되므로 행 집합 읽기를 마치기 전에 알림을 받을 수 있습니다. 규칙을 따르지 않는 쿼리를 전송하는 경우에도 즉시 알림을 받을 수 있습니다. 여기서 규칙은 앞에서 언급한 인덱싱된 뷰에 대한 규칙입니다.

알림을 사용하지 않는 경우: 교훈

이제 쿼리 알림이 작동하는 방식을 이해했으므로 이를 어디에 사용해야 할지 명백해졌습니다. 바로 읽기 위주 조회 테이블입니다. 각 알림 행 집합은 SQL Server의 리소스를 처리하므로 이를 읽기 전용 테이블에 사용하는 것은 여러모로 쓸데없는 낭비입니다. 또한 동시에 "감시"하는 서로 다른 행 집합 수만 지나치게 많아질 뿐이므로 임시 쿼리에도 사용할 필요가 없습니다. 유용한 내부 작업 관련 정보 한 가지는 SQL Server는 서로 다른 매개 변수 집합을 사용하는 매개 변수가 지정된 쿼리에 대해 알림 리소스를 끌어온다는 사실입니다. 위 예제의 SqlNotificationRequest에서처럼 매개 변수가 지정된 쿼리를 사용하면 항상 이러한 이점을 얻는 것은 물론 성능을 높일 수도 있습니다. 이 이야기를 듣고 염려할 수도 있겠지만 이처럼 강력한 기능을 사용한다고 해서 적절한 알림을 받지 않는 것은 아닙니다. user1이 A-M에서 au_lname인 제작자를 감시하고 user2가 au_lname 값을 매개 변수로 사용하여 N-Z에서 au_lname을 감시하는 경우 각 사용자는 각 하위 집합에 "해당하는" 알림만 받게 됩니다.

마지막으로 한 가지 주의할 점은 어떤 사람은 알림 응용 프로그램을 떠올릴 때 시장 가격이 변화하고 화면이 끊임없이 바뀌며 주식 거래자들로 가득 찬 공간을 상상한다는 점입니다. 이는 이 기능을 명백히 잘못 사용하는 것이며 그 이유는 다음과 같은 두 가지로 볼 수 있습니다.

  1. 행 집합은 계속해서 변하므로 네트워크는 쿼리 알림과 쿼리 새로 고침 요청으로 넘쳐 날 수 있습니다.
  2. 몇 명 이상의 사용자가 모두 같은 데이터를 "감시"하는 경우 각각의 알림으로 인해 많은 사용자가 동일한 결과에 대해 동시에 다시 쿼리하는 상황이 발생합니다. 이렇게 되면 SQL Server는 동일한 데이터에 대한 지나치게 많은 요청으로 넘치게 될 수 있습니다.

프로그래머가 이 기능을 잘못 사용할 수 있다고 생각되더라도 베타 2 이후의 SQL Server에서는 DBA가 동적 관리 뷰를 통해 이러한 기능을 모니터링할 수 있도록 하는 자세한 정보를 제공하므로 안심해도 됩니다. 현재 이들 뷰에서는 구독만 표시할 수 있습니다. SQL Server 2005에서는 항상 알림이 과도한 양의 리소스를 가져와 이를 제거하도록 "결정"할 수 있음을 기억하시기 바랍니다.

결론

쿼리 알림은 SQL Server 2005에 기본적으로 제공되는 강력하고 새로운 기능으로, ADO.NET 2.0과 ASP.NET 2.0에서 바로 사용할 수 있습니다. ADO.NET 기능(SqlNotificationRequestSqlDependency)은 SQL Server 2005 데이터베이스에 대해서만 작동하지만, ASP.NET에서는 폴링을 사용하는 대체 메커니즘을 통해 이 기능을 "이전 버전과도 호환하여" 사용할 수 있습니다. 이 기능은 구문 및 관련된 영향을 염두에 두고 신중하게 사용해야 합니다. 특히 이 기능의 핵심 부분은 ASP.NET에서 사용되는 읽기 위주 테이블입니다. 이러한 테이블은 다른 응용 프로그램은 물론 웹 응용 프로그램에서도 업데이트할 수 있습니다. 이 시나리오에서 쿼리 알림은 프로그래머들이 수년간 기다려 온 솔루션을 제공하고 있습니다.


,
STUDY/SQL Cache Dependency 2007. 4. 26. 15:04

[Study] SqlCacheDependency using DAAB and SQL Server 2005 - Enterprise Library 2.0

SqlCacheDependency using DAAB and SQL Server 2005 - Enterprise Library 2.0

SqlCacheDependency using DAAB and SQL Server 2005

by David Hayden ( .NET Developer )

 

I didn't think to include this in my previous post, SqlCacheDependency using ASP.NET 2.0 and SQL Server 2005, but with only a few minor changes you can use the Enterprise Library 2.0 DAAB with SqlCacheDependency and SQL Server 2005. For completeness I will include all the previous information, but the only code that changes is the code to create the command object and get the DataSet. The code is reduced by about 5 or 6 lines and there is no concern of connection management.

Here is now the previous article using the Enterprise Library 2.0 DAAB instead of SqlClient, except for the fact that we need to cast DbCommand to SqlCommand for use in the constructor of the SqlCacheDependency object.

 

SqlCacheDependency using ASP.NET 2.0 and SQL Server 2005 is a beautiful thing :) Although getting SqlCacheDependency to work with SQL Server 2000 is not rocket science, there are a few extra moving parts that need to be set-up in your web.config and on SQL Server 2000. When using SQL Server 2005, all of that goes away :)

 

Enable Service Broker

Before SqlCacheDependency will work with SQL Server 2005, you first have to enable Service Broker, which is reponsible for the notification services that let the web cache know a change has been made to the underlying database and that the item in the cache must be removed.


ALTER DATABASE Store SET ENABLE_BROKER;
GO

SqlCacheDependency.Start() in Global.asax

In ASP.NET, you need to run SqlCacheDependency.Start(connectionString) in the Global.asax:


void Application_Start(object sender, EventArgs e) 
{
    string connectionString = WebConfigurationManager.
        ConnectionStrings["Catalog"].ConnectionString;
    SqlDependency.Start(connectionString);
}

SqlCacheDependency in ASP.NET 2.0 Example

Now you can just create your SqlCacheDependency as normal in your ASP.NET 2.0 page. Here is a simple example:


public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DataTable categories = (DataTable)Cache.Get("Categories");

        if (categories == null)
        {
            categories = GetCategories();
            Label1.Text = System.DateTime.Now.ToString();
        }

        GridView1.DataSource = categories.DefaultView;
        GridView1.DataBind();
    }

    private DataTable GetCategories()
    {
        Database db = DatabaseFactory.CreateDatabase();

        SqlCommand command = (SqlCommand)db.
GetSqlStringCommand(
"SELECT CategoryID,Code,
Title FROM dbo.Categories
"); SqlCacheDependency dependency =
new SqlCacheDependency(command); DataSet dataset = db.ExecuteDataSet(command); DataTable categories = dataset.Tables[0]; Cache.Insert("Categories", categories, dependency); return categories;
} }

Since I am using a normal SELECT statement above, there are a number of rules one needs to follow, such as

  • You cannot use SELECT * - use individual fields
  • Must use fully qualified name of table, e.g. dbo.Categories

and a whole bunch of other rules outlined here on MSDN.

There are other ways to use SqlCacheDependency as well, such as with Ouput Caching. I can show those at another time.

Source: David Hayden ( .NET Developer )

Filed: ASP.NET 2.0, SQL Server 2005, Enterprise Library 2.0


posted on Sunday, April 30, 2006 4:09 PM

,
STUDY/SQL Cache Dependency 2007. 4. 25. 18:36

[Study] ASP.NET Caching: SQL Cache Dependency With SQL Server 2000

Introducing Cache Dependencies:

As time passes, the data source may change in response to other actions. However, if your code uses caching, you may remain unaware of the changes and continue using out-of-date information from the cache. To help mitigate this problem, ASP.NET supports cache dependencies. Cache dependencies allow you to make a cached item dependent on another resource so that when that resource changes the cached item is removed automatically. ASP.NET includes three types of dependencies:

  • Dependencies on other cache items.
  • Dependencies on files or folders.
  • Dependencies on a database query.

Introducing SQL Cache Notifications:

SQL cache dependencies are one of the most wonderful new features in ASP.NET 2.0, the ability to automatically invalidate a cached data object (such as a DataSet or Custom Data Type) when the related data is modified in the database. This feature is supported in both SQL Server 2005 and in SQL Server 2000, although the underlying plumbing is quite different.

Cache Notifications in SQL Server 2000:

Before you can use SQL Server cache invalidation, you need to enable notifications for the database. This task is performed with the aspnet_regsql.exe command-line utility, which is located in the c:\[WinDir]\Microsoft.NET\Framework\[Version] directory. To enable notifications, you need to use the -ed command-line switch. You also need to identify the server (use -E for a trusted connection and -S to choose a server other than the current computer) and the database (use -d). Here's an example that enables notifications for the Northwind database on the current server:

aspnet_regsql -ed -E -d Northwind
After executing this command, a new table named SqlCacheTablesForChangeNotification is added to the database Northwind. The SqlCacheTablesForChangeNotification table has three columns: tableName, notificationCreated, and changeId. This table is used to track changes. Essentially, when a change takes place, a record is written into this table. The SQL Server polling queries this table. Also a set of stored procedures is added to the database as well. See the following table.

Procedure Name Description
AspNet_SqlCacheRegisterTableStoredProcedure Sets a table up to support notifications. This process works by adding a notification trigger to the table, which will fire when any row is inserted, deleted, or updated.
AspNet_SqlCacheUnRegisterTableStoredProcedure Takes a registered table and removes the notification trigger so that notifications won't be generated.
AspNet_SqlCacheUpdateChangeIdStoredProcedure The notification trigger calls this stored procedure to update the AspNet_SqlCacheTablesForChangeNotification table, thereby indicating that the table has changed.
AspNet_SqlCacheQueryRegisteredTablesStoredProcedure Extracts just the table names fromthe AspNet_SqlCacheTablesForChangeNotification table. Used to get a quick look at all the registered tables.
AspNet_SqlCachePollingStoredProcedure Gets the list of changes from the AspNet_SqlCacheTablesForChangeNotification table. Used to perform the polling.

After this, need to enable notification support for each individual table. You can do this manually using the AspNet_SqlCacheRegisterTableStoredProcedure, to that, open your query analyzer and select your Database you've enabled for SQL Cache Notification for example Northwind database and write the following command:

exec AspNet_SqlCacheRegisterTableStoredProcedure 'TableName'

Or you can use aspnet_regsql, using the -et parameter to enable a able for sql cache dependency notifications and the -t parameter to name the table. Here's an example that enables notifications for the Employees table:

aspnet_regsql -et -E -d Northwind -t Products

Both options generates the notification trigger for the Products table as the following:

CREATE TRIGGER dbo.[Products_AspNet_SqlCacheNotification_Trigger] ON
[Products]
FOR INSERT, UPDATE, DELETE
AS
BEGIN

SET NOCOUNT ON
EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'Products'
END

So any record is inserted, deleted or updated in Products table will update ChangeId field in AspNet_SqlCacheTablesForChangeNotification table.

How Notificaions Works:

The AspNet_SqlCacheTablesForChangeNotification contains a single record for every table you're monitoring. When you make a change in the table (such as inserting ,deleting or updating a record), the changeId column is incremented by 1 -see AspNet_SqlCacheUpdateChangeIdStoredProcedure procedure-. ASP.NET queries this table repeatedly and keeps track of the most recent changeId values for every table. When this value changes in a subsequent read, ASP.NET knows that the table has changed.

In this scenario, Any change to the table is deemed to invalidate any query for that table. In other words, if you use this query:

SELECT * FROM Products WHERE CategoryID=1

The caching still works in the same way. That means if any product record is touched, even if the product resides in another category (and therefore isn't one of the cached records), the notification is still sent and the cached item is considered invalid. Also keep in mind it doesn't make sense to cache tables that change frequently.

Enable ASP.Net Polling:

To enable ASP.NET polling , you need to use the <sqlCacheDepency> element in the web.config file. Set the enabled attribute to true to turn it on, and set the pollTime attribute to the number of milliseconds between each poll. (The higher the poll time, the longer the potential delay before a change is detected.) You also need to supply the connection string information.

Creating the Cache Dependency:

Now that we've seen how to set up a database to support SQL Server notifications, the only remaining detail is the code, which is quite straightforward. We can use our cache dependency with programmatic data caching, a data source control, and output caching.

For programmatic data caching, we need to create a new SqlCacheDependency and supply that to the Cache.Insert() method. In the SqlCacheDependency constructor, you supply two strings. The first is the name of the database you defined in the element in the section of the web.config file e.g: Northwind. The second is the name of the linked table e.g: Products.

Example:

private static void CacheProductsList(List<NWProductItem> products)
{

SqlCacheDependency sqlDependency = new SqlCacheDependency("Northwind", "Products");
HttpContext.Current.Cache.Insert("ProductsList", products, sqlDependency, DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);

}

private static List<NWProductItem> GetCachedProductList()
{

return HttpContext.Current.Cache["ProductsList"] as List<NWProductItem>;

}

NWProductItem is business class, and here we are trying to cache a list of NWProductItem instead of DataSet or DataTable.

The following method is used by an ObjectDataSource Control to retrieve List of Products

public static List<NWProductItem> GetProductsList(int catId, string sortBy)
{

//Try to Get Products List from the Cache
List<NWProductItem> products = GetCachedProductList();
if (products == null)
{

//Products List not in the cache, so we need to query the Database by using a Data Layer
NWProductsDB db = new NWProductsDB(_connectionString);
DbDataReader reader = null;
products = new List<NWProductItem>(80);

if (catId > 0)
{

//Return Product List from the Data Layer
reader = db.GetProductsList(catId);

}
else
{

//Return Product List from the Data Layer
reader = db.GetProductsList();

}
//Create List of Products -List if NWProductItem-
products = BuildProductsList(reader);
reader.Close();

//Add entry to products list in the Cache
CacheProductsList(products);

}
products.Sort(new NWProductItemComparer(sortBy));

if (sortBy.Contains("DESC")) products.Reverse();
return products;

}

To perform the same trick with output caching, you simply need to set the SqlDependency property with the database dependency name and the table name, separated by a colon:

<%@ OutputCache Duration="600" SqlDependency="Northwind:Products" VaryByParam="none" %>

The same technique works with the SqlDataSource and ObjectDataSource controls:

<asp:SqlDataSource EnableCaching="True" SqlCacheDependency="Northwind:Products" ... />

Important Note:
ObjectDataSource doesn't support built in caching for Custom types such as the one in our example. It only support this feature for DataSets and DataTables.

To test this feature, download the attached demo. It contains an editable GridView. Set a break point at the GetProductList method and run in debug mode. Update any record and notice the changes. Also you can edit the solution and remove the cache dependency and note the deference after update.

Also you can remove the SqlDependency from the output cache in the OutputCaching.aspx page, and notice that whatever update you made to the data source, the page still retrieves the old version of data.

ASP.NET Caching: SQL Cache Dependency With SQL Server 2000

SQL cache dependencies are one of the most wonderful new features in ASP.NET 2.0, the ability to automatically invalidate a cached data object (such as a DataSet or Custom Data Type) when the related data is modified in the database. This feature is supported in both SQL Server 2005 and in SQL Server 2000, although the underlying plumbing is quite different.



[출처 : http://www.c-sharpcorner.com/UploadFile/mosessaur/sqlcachedependency01292006135138PM/sqlcachedependency.aspx?ArticleID=3caa7d32-dce0-44dc-8769-77f8448e76bc]
,
STUDY/SQL Cache Dependency 2007. 4. 25. 16:44

[Study] Wicked Code : Supporting Database Cache Dependencies in ASP.NET

[출처 : http://msdn.microsoft.com/msdnmag/issues/03/04/wickedcode/]
Developers love the ASP.NET application cache. One reason they love it is that ASP.NET lets them create dependencies between items placed in the cache and files in the file system. If a file targeted by a dependency changes, ASP.NET automatically removes dependent items from the cache. Combined with cache removal callbacks—notifications broadcast to interested parties when cached items are removed—cache dependencies are a boon to developers seeking to maximize performance by minimizing time-consuming file accesses because they permit file data to be cached without fear of it becoming stale.

As awesome as cache dependencies are, in ASP.NET version 1.0 they lack one critical feature that, if present, would qualify them as a developer's dream come true: support for database entities. In real life, most Web apps fetch data from databases, not files. But while ASP.NET is perfectly willing to link cached items to files, it is incapable of linking cached items to database entities. In other words, you can read the contents of a file into a DataSet, cache the DataSet, and have the DataSet automatically removed from the cache if the file it was initialized from changes. But you can't initialize a DataSet with a database query, cache the DataSet, and have the DataSet automatically discarded if the database changes. That's too bad because too many database accesses, like too much file I/O, is a performance killer.

The fact that ASP.NET doesn't support database dependencies doesn't mean database dependencies are impossible to achieve. This installment of Wicked Code presents a technique for extending the ASP.NET application cache to support database dependencies. It involves database triggers and extended stored procedures. Though the implementation presented here works exclusively with Microsoft® SQL Server™, the general technique is applicable to any database that supports triggers and user-defined procedures that interact with the file system.


Database Dependencies in Action

Let's begin with a demonstration. Figure 1 contains the source code for an ASP.NET page that displays randomly selected quotations from a SQL Server database named Quotes. To create the database, run the installation script shown in abbreviated form in Figure 2. The complete script is included in the downloadable zip file that accompanies this column. You can execute the script inside the SQL Server Query Analyzer or from the command line with an OSQL command.) Each time the page is fetched, Page_Load initializes a DataSet with all the records in the database's Quotations table, randomly selects a record from DataSet, and writes it to the page. Press F5 a few times and you'll see a random variety of quotations from some famous (and not-so-famous) people, as shown in Figure 3.

Figure 3 Random Quote
Figure 3 Random Quote

The page is called DumbDBQuotes.aspx for a reason. It's not very smart considering it queries the database each time it's requested. Accessing a database on every page access—especially a database hosted on a remote server—is a guaranteed way to build an application that won't scale.

The ASP.NET application cache is the solution to the problem of too many database accesses. If the DataSet were cached, it could be fetched directly from memory—that is, from the cache—thereby eliminating the redundant database accesses. It's easy enough to cache a DataSet; the application cache accepts instances of any type that derives from System.Object. In the Microsoft .NET Framework, that means instances of any managed type, including DataSet. The problem is that if you cache a DataSet and the database changes underneath it, you serve stale data to your users. You could implement a solution that requeries the database periodically, but the ideal solution is the one that requires no polling and that delivers fresh data from the data source the moment that data becomes available.

Take a look at Figure 4 and Figure 5, which contain the source code for a smarter quotes application. SmartDBQuotes.aspx doesn't retrieve quotations from the database; it gets them from the application cache. Global.asax primes the cache and refreshes it if the database changes. Here are directions for taking them for a test drive:

  1. Create a subdirectory named AspNetSql in the root directory of your Web server's C: drive. Inside AspNetSql, create a zero-byte file named Quotes.Quotations. Make sure Everyone, or at least SYSTEM and ASPNET (a special account created when ASP.NET is installed), has access to Quotes.Quotations.
  2. Copy XSP.dll, which is included in this column's downloadable code sample, into the SQL Server binn directory (for example, C:\Program Files\Microsoft SQL Server\MSSQL\Binn) or to any location on your Web server that Windows will automatically search for DLLs (for example, C:\Windows\System32).
  3. Rebuild the database using the modified script in Figure 6.
  4. Deploy Global.asax and SmartDBQuotes.aspx to a virtual directory on your Web server (for example, wwwroot).
  5. Request SmartDBQuotes.aspx in your browser. Refresh the page a few times until the quote "The use of COBOL cripples the mind; its teaching should therefore be regarded as a criminal offense" appears.
  6. Use SQL Server Enterprise Manager or the tool of your choice to modify the quotation in the Quotes database's Quotations table. Change it to read "The use of Visual Basic® enriches the mind; its teaching should therefore not be regarded as a criminal offense." Then refresh the page until the modified quotation appears. Observe that the new quotation appears, not the old one, even though the query results are now being stored in the application cache.

You just demonstrated that the ASP.NET application cache can be combined with database dependencies to produce high-volume, data-driven applications. The question is how? How was a link formed between the cached DataSet and the database, and how scalable is the solution?

Back to top

How Database Dependencies Work

On the outside, both DumbDBQuotes.aspx and SmartDBQuotes.aspx look the same, producing identical output. On the inside, they could hardly be more different. The former performs a database access every time it's requested; the latter fetches data from the application cache. Moreover, SmartDBQuotes.aspx uses a database dependency to ensure that if the database changes, the cached data changes, too. If the database doesn't change, the database is queried just once during the lifetime of the application. If the database changes, one more query updates the cache.

Figure 7 Database Dependencies
Figure 7 Database Dependencies

Figure 7 illustrates how the database dependency works. When it places the DataSet in the cache, Global.asax creates a file-system dependency between the DataSet and a file named Quotes.Quotations in the C:\AspNetSql directory. Quotes.Quotations is a zero-byte signal file—a file whose only purpose is to trigger the ASP.NET application cache-removal logic. Here's the statement in Global.asax that creates the CacheDependency object linking the DataSet to Quotes.Quotations:

new CacheDependency ("C:\\AspNetSql\\Quotes.Quotations")
Global.asax also registers its own RefreshCache method to be called when the DataSet is removed from the cache—that is, when the signal file changes:
new CacheItemRemovedCallback (RefreshCache)

RefreshCache's job is to query the database and place the resulting DataSet in the application cache. It's called once when the application starts up and again when—or if—the DataSet is removed from the cache.

That's half of the equation. The other half involves the database. The revised database installation script in Figure 6 adds an insert/update/delete trigger to the database's Quotations table:

CREATE TRIGGER DataChanged ON Quotations
FOR INSERT, UPDATE, DELETE
AS EXEC master..xsp_UpdateSignalFile 'Quotes.Quotations'
GO
The trigger fires when records are added to or deleted from the table and when records change. What does the trigger do? It calls an extended stored procedure—the SQL Server euphemism for code in a Win32® DLL—named xsp_UpdateSignalFile. The extended stored procedure, in turn, uses the Win32 CreateFile function to update Quotes.Quotations' time stamp.

The cached DataSet's lifetime is tied to Quotes.Quotations using an ordinary file-system cache dependency; updating the Quotations table causes a database trigger to fire; and the trigger calls an extended stored procedure that "updates" Quotes.Quotations, prompting ASP.NET to remove the DataSet from the application cache and call Global.asax's RefreshCache method, which then performs a brand new database query and starts the whole process all over again.

The final piece of the puzzle is the extended stored procedure. It's housed in XSP.dll, the DLL that you installed earlier. I wrote XSP.dll in unmanaged C++ using Visual C++® 6.0. Its source code appears in Figure 8. The path to the signal file—C:\AspNetSql—is hardcoded into the DLL, but you can change that if you'd like and make it an input parameter just like the file name.

Extended stored procedures must be installed before they're used. The following statements in the SQL installation script that you executed install xsp_UpdateSignalFile in the master database and grant execute permission to all comers:

USE master
EXEC sp_addextendedproc 'xsp_UpdateSignalFile', 'XSP.dll'
GRANT EXECUTE ON xsp_UpdateSignalFile TO PUBLIC
GO
Why write a custom extended stored procedure to update a file's time stamp when a built-in extended stored procedure such as xp_cmdshell could be used instead? The reason is security—xp_cmdshell can be used for all sorts of malicious purposes, while xsp_UpdateSignalFile cannot. Because xsp_UpdateSignalFile does little more than call the Windows CreateFile function, it is also more efficient than xp_cmdshell.
Back to top

Server Farms

SmartDBQuotes.aspx and friends work great if the Web server and the database server live on the same machine, but what if the database is installed on a different machine? And what about Web farms? Would a change notification mechanism based on database triggers, extended stored procedures, and file-system dependencies be compatible with multiserver installations?

You bet. Under the hood, ASP.NET cache dependencies that are based on the file system rely on Win32 file change notifications. And Win32 file change notifications support Universal Naming Convention (UNC) path names. To take advantage of database cache dependencies on Web farms, let the signal file reside on the database server, as shown in Figure 9. Then pass CacheDependency's constructor a UNC path name specifying the signal file's network address:

new CacheDependency 
(@"\\ServerName\AspNetSql\Quotes.Quotations"),
Figure 9 Signal File on Database Server
Figure 9 Signal File on Database Server

The greatest obstacle to creating dependencies that target remote files is security. By default, the ASP.NET worker process runs as ASPNET when paired with Microsoft Internet Information Services (IIS) 5.0 and when configured to run in compatibility mode under IIS 6.0. ASPNET is a local account that can't authenticate on remote machines. Without configuration changes, attempting to create a cache dependency with a UNC path name produces an access denied error—even if you give Everyone access to the remote share.

Several solutions exist. One is to configure ASP.NET to use a domain account that can be authenticated on the database server. That change is easy enough to accomplish: you simply specify the account's user name and password in the <processModel> section of each Web server's Machine.config. Many companies, however, have security policies that prevent passwords from being stored in plaintext configuration files. If that's true of your company, but you'd still like to run ASP.NET using a domain account, you can either upgrade to version 1.1 of the .NET Framework (which allows worker process credentials to be encrypted and stored securely in the registry) or download a hotfix for version 1.0 that does the same. You'll find information about the hotfix at Stronger Credentials for processModel, identity, and sessionState.

A variation on this technique involves setting up identical local accounts (using the same user name and password) on both machines and configuring ASP.NET to run as that identical local account on the Web server.

Another solution to the problem of authenticating on a back-end database server containing a signal file is to upgrade to Windows Server 2003. The latest addition to the Windows Server family comes with IIS 6.0, which allows the ASP.NET worker process to run using the identity of Network Service. Unlike ASPNET, Network Service can perform authentication on remote machines. Or you could pull an old trick out of the ASP playbook and access the database through a COM+ component running on the Web server and configure the component to run as a principal that has network credentials.

However you choose to make the remote signal file accessible to your ASP.NET app, the bottom line is that combining database cache dependencies with UNC path names delivers a scalable solution that works equally well for Web farms and single-server installations. That's good news for developers who are using ASP.NET to build high-volume, data-driven applications—and good news for users as well.

,
STUDY/SQL Cache Dependency 2007. 4. 25. 15:58

[Study] Using the SQL Server Cache Dependency

1. SQL Cache 종속성을 사용하도록 데이터베이스 설정
aspnet_regsql -S localhost -U sa -P password -d testDB -ed

2. SQL Cache 종속성을 테이블에 활성화시킨다.
aspnet_regsql -S localhost -U sa -P password -d testDB -t tblMember -et

3. SQL Cache 종속성이 설정된 테이블 목록을 조회한다.
aspnet_regsql –S localhost –U sa –P password –d testDB -lt

4. SQL Server Cache Dependency 설정 해제 명령어

1) DB 해제
aspnet_regsql -S localhost -U sa -P password -d testDB -dd

2) Table 해제
aspnet_regsql -S localhost -U sa -P password -d testDB -t tblMember -dt

5. Web.config 설정

<caching>

    <sqlCacheDependency enabled="true">

      <databases>

        <add name="Northwind" connectionStringName="ConnectionString" pollTime="500" />

      </databases>

    </sqlCacheDependency>

</caching>

è  connectionStringName web.config 설정된 ConncetionString값과 동일하게 설정함.

è  pollTime 단위 : milliseconds

G.      SQL Server Cache Dependency 페이지 적용 (aspx page)
<%
@ OutputCache Duration="3600" VaryByParam="none"
SqlDependency="Northwind:Customers" %>

Or

<%@ OutputCache Duration="3600" VaryByParam="none"
SqlDependency="Northwind:Customers;Northwind:Products" %>

è  SqlDependency = “DatabaseName:TableName”


[출처 : http://blog.naver.com/metalzang?Redirect=Log&logNo=130016491535]
,
TOTAL TODAY