Caching NameValueList (and other lists too)

Caching NameValueList (and other lists too)

Old forum URL: forums.lhotka.net/forums/t/10298.aspx


tiago posted on Friday, April 22, 2011

You might be familiar with a common structure for NameValueLists

    private static DocClassNVL _list;
 
    /// <summary>
    /// Clears the in-memory DocClassNVL cache so it is reloaded on the next request.
    /// </summary>
    public static void InvalidateCache()
    {
        _list = null;
    }
 
    /// <summary>
    /// Used by async loaders to load the cache.
    /// </summary>
    /// <param name="list">The list to cache.</param>
    internal static void SetCache(DocClassNVL list)
    {
        _list = list;
    }
 
    internal static bool IsCached()
    {
        return _list != null;
    }
 
 
    /// <summary>
    /// Factory method. Loads an existing <see cref="DocClassNVL"/> object from the database.
    /// </summary>
    /// <returns>A reference to the fetched <see cref="DocClassNVL"/> object.</returns>
    public static DocClassNVL GetDocClassNVL()
    {
        if (_list == null)
            _list = DataPortal.Fetch<DocClassNVL>();
 
        return _list;
    }
 
 
    /// <summary>
    /// Initializes a new instance of the <see cref="DocClassNVL"/> class.
    /// </summary>
    /// <remarks> This isn't a public access method in order to prevent direct creation.
    /// Use factory methods instead.</remarks>
    private DocClassNVL()
    {
        // Prevent direct creation
    }

The static _list field holds the data and avoids the need to go SQL Server everytime you need it. The cache is client side or server side but if you use the list both in client code and in server code, you will hit the database twice. In fact static fields aren't serialized so client side code never knows about the cache build on the server side (and vice versa). Most of the time you don't use the list server side. Nevertheless you will hit the database for every user that needs to use it. The only way I know of avoiding this problem is to let the DataPortal know about the cache.

    /// <summary>
    /// Load <see cref="DocClassNVL"/> collection from the database.
    /// </summary>
    protected void DataPortal_Fetch()
    {
        if (IsCached())
        {
            LoadCachedList();
            return;
        }
 
        using (var cn = new SqlConnection("DocStore"))
        {
            using (var cmd = new SqlCommand("GetDocClassNVL", cn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cn.Open();
                LoadCollection(cmd);
            }
        }
    }
 
    private void LoadCachedList()
    {
        IsReadOnly = false;
        var rlce = RaiseListChangedEvents;
        RaiseListChangedEvents = false;
        AddRange(_list);
        RaiseListChangedEvents = rlce;
        IsReadOnly = true;
    }
 
    private void LoadCollection(SqlCommand cmd)
    {
        IsReadOnly = false;
        var rlce = RaiseListChangedEvents;
        RaiseListChangedEvents = false;
        using (var dr = new SafeDataReader(cmd.ExecuteReader()))
        {
            while (dr.Read())
            {
                Add(new NameValuePair(
                    dr.GetInt32("DocClassID"),
                    dr.GetString("DocClassName")));
            }
        }
        RaiseListChangedEvents = rlce;
        IsReadOnly = true;
    }

The DataPortal_Fetch() checks whether the list is cached. If the list is cached, LoadCachedList()
is called and DataPortal_Fetch() returns without further ado (meaning it doesn't hit the SQL Server).

PS - You can cache a ReadOnlyList the same way i.e. using the same code structure.

bniemyjski replied on Monday, April 25, 2011

It would be nice to set expirations on how long the data is cached for. Also, for ReadOnlyLists, you would want to cache by the a hash of the Criteria because the returned results could be filtered.

tiago replied on Monday, April 25, 2011

Hi Blake,

Blake Niemyjski

It would be nice to set expirations on how long the data is cached for.

There is a very powerful caching library available at CSLA .NET Contrib and I didn't want to duplicate its advanced features.

Blake Niemyjski

Also, for ReadOnlyLists, you would want to cache by the a hash of the Criteria because the returned results could be filtered.

Thought about that but chose to ignore cache altogether for filtered lists. The use case was ReadOnly List as replacement for NameValueList (say to build a hierarchical combo box, where you need ID, Value and ParentID). Usually you get a filtered list, after a search. If you are searching, chances are you doing some configuration work: adding, removing or editing list members. And you don't want caching as the list is supposed to change.

There must be other scenarios were caching filtered lists is useful. But again there should be a cache expiration policy, etc. So this is just "Simple Cache".

RockfordLhotka replied on Monday, April 25, 2011

On the server side you can also use AppFabric caching. This is a distributed cache, and so works across a server farm too - very nice stuff.

Copyright (c) Marimer LLC