How to Organize Cache on the Server Side of the Silverlight Viewer for Reporting Services.

Vitaliy Korney

Cache is an important part of the reactive application. Let’s imagine that we don’t have any cache on the server side. In that case every request for every page of your report will cause document rendering. Some documents can be rather huge and it’s not efficient to call document rendering. In the ideal situation we should call document rendering once. On the other side we shouldn’t think about cache like about durable storage on the server side.

We have several implementations of cache on the server side. You may use them in your applications. Their names are SessionCache and AspNetCache. SessionCache as you can guess is based on session as well as AspNetCache based on the AspNet Cache.

You may think that all the problems are solved. But sometimes document is not living for a long time in the cache for some reasons. Today I’ll show you how to organize cache based on the MS SQL database storage. It won’t be cache no longer in the direct meaning but storage which is available on the server side. Real cache is stored in memory. The main idea is to prevent multiple rendering of the document.

There is an interface PerpetuumSoft.ReportingServices.Viewer.Server.IReportCache, which we should implement in our “cache” substitute. Let’s call this “cache” StorageCache.

Here is a code of our StorageCache implementation:

public class StorageCache : PerpetuumSoft.ReportingServices.Viewer.Server.IReportCache
    {
        private AspNetCache rapidCache = new AspNetCache();
        public virtual void AddDocumentToCache(string key, object value, params object[] parameters)
        {
            DocumentStorage dataSet = new DocumentStorage();
            //Add document to AspNet Cache
            rapidCache.AddDocumentToCache(key, value, parameters);
            //Add document to database
            AddToDB(key,value);
        }

        private void AddToDB(string key, object value)
        {
            try
            {
                //create dataset
                DocumentStorage storageDS = new DocumentStorage();
                DataRow dr = storageDS._DocumentStorage.NewRow();
                //add key to database
                dr["docKey"] = key;
                //serialize
                BinaryFormatter serializer = new BinaryFormatter();
                MemoryStream memStream = new MemoryStream();
                serializer.Serialize(memStream, value);
                memStream.Position = 0;
                //add document value
                dr["docVal"] = memStream.ToArray();
                storageDS._DocumentStorage.Rows.Add(dr);
                //check if the record already exists
                DocumentStorageTableAdapters.DocumentStorageTableAdapter tableAdapter = new DocumentStorageTableAdapters.DocumentStorageTableAdapter();
                SampleApplicationSL4.Web.DocumentStorage.DocumentStorageDataTable table = tableAdapter.GetDataByKey(key);
                if (table.Rows.Count == 0)
                {
                    //add
                    tableAdapter.Update(storageDS._DocumentStorage);
                }
                else
                {
                    //update
                    table.Rows[0]["docVal"]=memStream.ToString();
                    tableAdapter.Update(table);
                }
                memStream.Flush();
            }
            catch (Exception ee)
            {
            }
        }

        private object GetFromDB(string key)
        {
            DataTable docTable=null;
            try
            {
                //retrieves data from database
                DocumentStorageTableAdapters.DocumentStorageTableAdapter adapter = new DocumentStorageTableAdapters.DocumentStorageTableAdapter();
                docTable = adapter.GetDataByKey(key);
            }
            catch(Exception ee)
            {
                CoreLogger.Error("GetFromDB", ee);
            }
            if (docTable.Rows.Count > 0)
            {
                //deserialize
                byte[] result = (byte[]) docTable.Rows[0]["docVal"];
                MemoryStream memStream = new MemoryStream(result);
                BinaryFormatter serializer = new BinaryFormatter();
                object res = serializer.Deserialize(memStream);
                memStream.Flush();
                return res;
            }
            return null;
        }

        public virtual object GetDocumentFromCache(string key)
        {
            if (key == null) return null;
            //try to retrieve from Asp.net cache
            object result = rapidCache.GetDocumentFromCache(key);
            if (result == null)
            {
                //durable storage request
                result = GetFromDB(key);
                rapidCache.AddDocumentToCache(key, result);
            }
            return result;
        }

        public void AddExecutionToCache(string executionId, object executionCacheObject)
        {
            //add to Asp.net cache
            rapidCache.AddExecutionToCache(executionId, executionCacheObject);
            //add to durable storage
            AddToDB(executionId, executionCacheObject);
        }

        public object GetExecutionFromCache(string executionId)
        {
            //try to retrieve data from Asp.net cache
            object result = rapidCache.GetExecutionFromCache(executionId);
            if (result == null)
            {//retrieve data from durable storage
                result = GetFromDB(executionId);
                rapidCache.AddExecutionToCache(executionId, result);
            }
            return result;
        }

        public void DeleteFromCache(string key)
        {
            DocumentStorageTableAdapters.DocumentStorageTableAdapter ta = new DocumentStorageTableAdapters.DocumentStorageTableAdapter();
            //Delete object from Asp.net cache
            rapidCache.DeleteFromCache(key);
            //Delete object from the database
            ta.DeleteByKey(key);
        }
    }
In your ReportService implementation you need to override CreateCache method:
    public class ReportService : ReportServiceRemote
    {
        protected override IReportCache CreateCache()
        {
            return new StorageCache();
        }
    }

Of course this is only a demo, which demonstrates how you can override cache behavior and if you want to add this solution in your live system you might also think about deleting useless information from the database and to store timestamp field for that purposes in your database. You may schedule a deleting job for your SQL Agent to be run in the specific time interval.

August 5th, 2011

Leave a Comment