Library tutorials & articles

.NET Data Caching

A Real-World Example

Over a year ago I had written another article on 4Guys discussing a method of paging a .NET datagrid with exact count - Custom ASP.NET Datagrid Paging With Exact Count. I went on to show how you can cleverly page a datagrid and always show how many rows are coming up, like "Next 5 >" and right before the last page you'll see "Next 1 >", for instance, assuming there would be just one record to be displayed on the last page.

Now this was cool, and the emails received attested this fact that people enjoyed it and found it useful. Nevertheless, as Scott Mitchell pointed out, the DataGrid by default has one particular flaw. This flaw in question is when displaying your DataGrid result set with say 5,000 records, upon each consecutive paging action, the database get hit again, and pulls in all 5,000 records in again! This is an obvious performance issue that does affect the application and can greatly diminish scalability.

There are other means to remedy this. One way is creating a stored procedure that returns only the pertinent records, as discussed at: Paging through Records using a Stored Procedure. I have used this method on occasion prior to utilizing .NET caching - it works well but the amount of code you need to write is quite substansive. Additionally, even with the stored procedure approach the database must be hit each time the user pages through the data.

By caching the result set in the data cache you can allow the user to page through the cached data, thereby not enduring any database hits except for the first time the item is loaded into the cache (and any other time it gets evicted and needs to be reinserted into the cache). The only potential downside is that the cached data may become stale over time as the underlying database data changes. With some clever programming, though, you can set up your database inserts, updates, and deletes such that when new data is added to the underlying database the cached data is invalidated. See Invalidating an ASP.NET Web Application Cache Item from SQL Server for more details.

The caching for my example resides solely in the BindMyDataGrid() method, which is responsible for binding the data to the DataGrid. The first line of code in this subroutine grabs the cached DataSet from the data cache, as can be seen below:

Sub BindMyDataGrid()
'Programmatic Caching Setup
Dim DataGridCache As DataSet = CType(Cache.Get("DataGridCache"),DataSet)

...

Of course the DataSet might not exist in the cache. We might not have added it, or it may have expired or been evicted. Hence, before doing anything else we must check to see if DataGridCache is equal to Nothing . If it is, then we must populate our DataSet from the database and store it back in the cache. If it is not, then we can just proceed to the code that binds the DataSet to the DataGrid.

'Continued from previous code block...
...
If DataGridCache is Nothing Then
    'Populate the DataSet with data from the database
    Const CommandText As String = _
        "SELECT FAQID, Description FROM tblFAQ ORDER BY FAQID"
    'The connection to our database
    Dim myConnection as New _
        SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
    Dim myCommand As New SqlDataAdapter(CommandText, myConnection)
    Dim DS As New DataSet()
    myCommand.Fill(DS)
   
    'Specify the DataSource for the DataGrid is the DataSet
    'we just populated
    MyDataGrid.DataSource = DS
    'Now insert dataset into cache, specifying that it should
    'expire in 10 minutes
    Cache.Insert ("DataGridCache", DS, Nothing, _
            DateTime.Now.AddMinutes(10), TimeSpan.Zero)
    lblCacheInfo.text = "DataGrid was populated from the database..."
    'Specify what time the cache was updated
    Application("TimeCachedDataSetAdded") = DateTime.Now
   
    'Determine how many total records we have
    RcdCount = CInt(DS.Tables(0).Rows.Count.ToString())
Else
    'The DataSet is in the cache.
    lblCacheInfo.text = "DataGrid was used from cache. The cache " & _
        was populated at " & _
        Application("TimeCachedDataSetAdded").ToString()
    'Populate datagrid from cache.
    MyDataGrid.DataSource = DataGridCache
   
    'Calculate the total # of records
    RcdCount = CInt(DataGridCache.Tables(0).Rows.Count.ToString())
End If

'Bind the datagrid from either source
MyDataGrid.DataBind()
ShowStats() 'Displays what page we're on and such
End Sub

Other than the BindMyDataGrid() method shown above the only other important part of the ASP.NET Web page is the HTML section, which sets certain DataGrid properties to allow for paging.

<asp:Label id="lblCacheInfo" runat="server" />
...
<form runat="server">
<asp:datagrid id="MyDataGrid" runat="server" Font-Bold="True"
        AutoGenerateColumns="True"
        AllowPaging="True" PageSize="10"
        OnPageIndexChanged="MyDataGrid_Page" />
</form>

The more important properties of the DataGrid Web control have been bolded . For more information on the DataGrid be sure to read: An Extensive Examination of the DataGrid Web Control .

Summary

In this article we examined the awesome power and useful features of the .NET caching API. Specifically, in this article we looked at a real-world example where the data for a pagable DataGrid was cached in the data cache, saving round trips to the database each time the user steps through a page of data.

Until next time, happy programming!

This article was originally published on 4guysfromrolla.com.

Comments

  1. 15 May 2008 at 15:11

    To Access the Pages Cache from class code c#

     

    Cache ce = new Cache();

     

    ce = System.Web.HttpRuntime.Cache; Lol

  2. 30 Apr 2005 at 23:05

    So , Does That mean  this is a Genuine Microsoft Bug??
    No matter What If I Use this Syntax[ Colorede red] to acess the Cache Object I get an Exception...
    internal class EnvUtil ystem.Web.UI.Page {
               
    internal EnvUtil()
    {


    }
    internal  string ResetClientID
    {
    get
    {
    string sResetClientId="";
    try
    {
      if (Cache["ResetClientDoc"] == null)     {
               TextReader xt=new StreamReader(Server.MapPath(@"../../Content/EnvReport/ResetClientID.xml"));


               sResetClientId=xt.ReadToEnd();
               xt.Close();
               CacheDependency dep = new CacheDependency(Server.MapPath(@"../../Content/EnvReport/ResetClientID.xml"), DateTime.Now);


               Cache.Insert("ResetClientDoc", sResetClientId, dep);
       }
       else
       {
               sResetClientId=(string) Cache["ResetClientDoc"];
       }


    }
    catch (Exception ex)
    {
       Debug.WriteLine(ex.Message);
    }
       return sResetClientId;
    }
    }


    }
    }



    Output
    ?ex.Message
    "Cache is not available"



  3. 30 Apr 2005 at 10:01

    Just because the Cache cannot be inherited, doesn't mean you can't access it when you are inheriting the Page class -


    public class MyClass : System.Web.UI.Page {
      public Object SomeMethod() {
         return Cache["SomeObject"];
      }
    }


    is fine. As it's sealed, what you can't do is this:


    public class MyClass : Cache {
      // override or add some methods here
    }

  4. 30 Apr 2005 at 05:00

    I stumbled into this problem. The comment is correct The Cache class is a sealed class, which means it cannot be inherited. Since you are inheriting the Page class in your X testClass the Cache object will  not be available. One way to solve the problem is to pass the Cache object of the Page to the Method that need to process the Cache information as shown in the Sample code..



    <%@ import Namespace="Env" %>
    <%@ import Namespace="System.Xml" %>
    <%@ Page Language="C#" Inherits ="Env.ServerProcess" %>
    <%
       
    XmlDocument xDoc = new  XmlDocument();
    xDoc=ProcessRequest(Request.InputStream,this.Cache);
    Response.ContentType="text/xml";
    Response.Write (xDoc.OuterXml);
    %>
    public class ServerProcess : System.Web.UI.Page
    {
           
       public  XmlDocument ProcessRequest(Stream PageRequestStream,Cache MyCache)
           {
               string sResetClientId="";
              XmlDocument xdoc =new XmlDocument();
                try{
                  if (MyCache ["ResetClientDoc"] == null){
           TextReader xt=new StreamReader(Server.MapPath(@"Content/ResetClientID.xml"));
           sResetClientId=xt.ReadToEnd();
           xt.Close();
           MyCache dep = new CacheDependency(Server.MapPath(@"Content/ResetClientID.xml"), DateTime.Now);
                   
           MyCache.Insert("ResetClientDoc", sResetClientId, dep);}
           Else
    {
              sResetClientId=(string) MyCache ["ResetClientDoc"];


           }
                                 }
                          catch (Exception e){
           System.Diagnostics.Debug.WriteLine( e.Message.ToString())
       }
    xdoc.LoadXml(sResetClientId);
    return xdoc;
       
    }

  5. 23 Mar 2005 at 14:15

    Hi Charlie,

    You can use .NET remoting when you need to keep your cache across processes. The process is discussed here Using Remoting Singleton Caching.

    Hope this helps.

    -DM
  6. 23 Mar 2005 at 14:08
    Hi all, this article unfortunately doesn't deal with class caching. However, in my latest article posted here on DeveloperFusion -Building a Full-Featured Custom DataGrid Control -  I do just that. I demonstrate how to cache an object, in this case the Datagrid, within a class using either the Session or Web Cache API.

    As for caching across processes, you'll need to use .NET remoting. The process is discussed here The process is discussed here Using Remoting Singleton Caching.

    Hope this helps.

    -DM
  7. 16 Jul 2004 at 02:33

    Yes I have tried that as well. IMHO: The thing is that you are trying to access a cache object from a class. But the documentation says that the Cache object cannot be inherited. therefore I would expect it to throw a "Cache is not available" exception. Now this is a real bugger specially when you want to cache data across process/pages. This is precisely what I'm trying to do right now and I have yet to find a solution. Help anyone?

  8. 07 Apr 2004 at 15:17

    How would one use the .NET cache object to keep caches accurate across processes?


    --Charlie



  9. 01 Mar 2004 at 09:55

    On what line does the error occur?

  10. 19 Feb 2004 at 09:53
    I created a class to test the data cache. Any help where to use cache?

    // test.cs
    //

    namespace xtest {
       using System;
       using System.Web;
       using System.Web.Caching;
       public class xtest {

           public static Cache srvCache = new Cache();

           public xtest() {}
           public static string getCache(string key){

               try {
               object strServer= 0;

                   if (srvCache["ServerString"] == null)
                     srvCache["ServerString"]=key;

                   strServer = srvCache.Get("ServerString");

                   return (string) strServer;
               }
               catch (Exception e)
               {
                   return "error: getCache() - "+ e.Message;
               }

           }
       }
    }

    When I run the below mentioned aspx. I get error:
                                   - Object reference not set to an instance of an object.


    <%@ Page Language="C#" %>
    <%@ import Namespace="xtest" %>
    <script runat="server">

       void Page_Load(object sender, EventArgs e) {


                Response.Write(xtest.getCache("X-STAGE"));

       }

    </script>
    <html>
    <head>
    </head>
    <body>
       <form runat="server">
           <!-- Insert content here -->
       </form>
    </body>
    </html>


  11. 01 Jan 1999 at 00:00

    This thread is for discussions of .NET Data Caching.

Leave a comment

Sign in or Join us (it's free).

Dimitrios Markatos Dimitrios, or Jimmy as his friends call him, is a .NET developer/architect who specializes in Microsoft Technologies for creating high-performance and scalable data-driven enterprise Web and deskto...

Related podcasts

Events coming up

  • Mar 15

    DevWeek 2010

    London, United Kingdom

    DevWeek is Europe’s leading independent conference for software developers, database professionals and IT architects, and features expert speakers on a wide range of topics, including .NET 4.0, Silverlight 3, WCF 4, Visual Studio 2010, REST, Windows Workflow 4, Thread Synchronization, ASP.NET 4.0, SQL Server 2008 R2, LINQ, Unit Testing, CLR & C# 4.0, .NET Patterns, WPF 4, F#, Windows Azure, ADO.NET, Entity Framework, Debugging, T-SQL Tips & Tricks, and more.

Want to stay in touch with what's going on? Follow us on twitter!