Caching WCF RIA Services data on the server

Share on Facebook

 

In a previous post on impleneting caching with the Entity Framework 4.1, I approached the task by creating an implementation of IDbSet<TEntity> which is the interface implemented and used by DbContext to maintain entity sets with the concrete DbSet<TEntity>. However, my actual requirement was to cache RIA service data on the server side and although abstracting the caching mechanism to the underlying data access technology would have been elegant and more reusable, I ended up using a simpler less elegant solution which got the job done for this particular project.

So what is this simpler approach? Well I introduced a cache field on the RIA Service class like so:

private ICacheProvider Cache = new DefaultCacheProvider();

The ICaheProvider interface could be as bloated or succint as you like but because RIA services does not support generic types (The underlying transport mechanism of WCF does not allow for a schema type supporting generics. Everything must be concrete classes), I opted for a plain and simple interface as shown below:

    
public interface ICacheProvider
    {
        object Get(string key);
        void Set(string key, object data, int cacheTime);
        bool IsSet(string key);
        void Invalidate(string key);
    }

The default implementation of the ICacheProvider interface using the .Net framework 4.0 caching objects ObjectCache and MemoryCache is as follows:

   
public class DefaultCacheProvider : ICacheProvider
    {
        private ObjectCache Cache { get { return MemoryCache.Default; } }

        public object Get(string key)
        {
            return Cache[key];
        }

        public void Set(string key, object data, int cacheTime)
        {
            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromSeconds(cacheTime);
            Cache.Add(new CacheItem(key, data), policy);
        }

        public bool IsSet(string key)
        {
            return (Cache[key] != null);
        }

        public void Invalidate(string key)
        {
            Cache.Remove(key);
        }
    }

In the RIA services class, a cache key is used to keep track of each unique set of data. It is important to note that, the cached objects cannot be IQueryable<TEntity> because entity graphs would not be fully materialised if we cache the IQueryable objects, rather we have to execute the queries by calling ToList() extension on the query and then cache the actual list of objects. When we fetch data, we first check the cache for an existing set, and notice also the way we check the cache: We cast the object at the time of checking rather than check if the object is in the cache(by using the boolean IsSet interface method), this is because the little time delay in checking before fetching could pose problems in multi-threaded environments.

Finally, we invalidate the cache on any data modification service call. The relevant code for an entity called "Project" maintained through the RIA service class is shown below:

   
        // TODO:
        // Consider constraining the results of your query method.  If you need additional input you can
        // add parameters to this method or create additional query methods with different names.
        // To support paging you will need to add ordering to the 'Projects' query.
        [AdminUsersOnly]
        public IQueryable<Project> GetProjects()
        {
            return this.DbContext.Projects.Include("Phases.PhaseType").OrderBy(p => p.StartDate);           �
        }

        public IQueryable<Project> GetActiveProjects()
        {           �
            var projects = (List<Project>)Cache.Get("ACTIVEPROJECTS");
            if (projects == null)
            {               �
                projects = this.DbContext.Projects.Include("Phases.PhaseType").Where(p => p.Phases.Any(ph => ph.PhaseDate >= DateTime.Today)).OrderBy(p => p.Phases.OrderBy(ph => ph.PhaseDate).FirstOrDefault().PhaseDate).ToList();

                if (projects.Any())
                {
                    Cache.Set("ACTIVEPROJECTS", projects, cacheDurationInSecs);
                }
            }
            return projects.AsQueryable();
        }

        [AdminUsersOnly]
        public void InsertProject(Project project)
        {
            Cache.Invalidate("ACTIVEPROJECTS");
            DbEntityEntry<Project> entityEntry = this.DbContext.Entry(project);
            if ((entityEntry.State != EntityState.Detached))
            {
                entityEntry.State = EntityState.Added;
            }
            else
            {
                this.DbContext.Projects.Add(project);
            }
        }

        [AdminUsersOnly]
        public void UpdateProject(Project currentProject)
        {
            Cache.Invalidate("ACTIVEPROJECTS");
            this.DbContext.Projects.AttachAsModified(currentProject, this.ChangeSet.GetOriginal(currentProject), this.DbContext);
        }

        [AdminUsersOnly]
        public void DeleteProject(Project project)
        {
            Cache.Invalidate("ACTIVEPROJECTS");
            DbEntityEntry<Project> entityEntry = this.DbContext.Entry(project);
            if ((entityEntry.State != EntityState.Deleted))
            {
                entityEntry.State = EntityState.Deleted;
            }
            else
            {
                this.DbContext.Projects.Attach(project);
                this.DbContext.Projects.Remove(project);
            }
        }

 

It might not be the most elegant, but the project I was working on was time constrained, and under the circumstances, this got the job done.
Hope you find it useful.

 

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListkick it on DotNetKicks.comTwitThis

Comments are closed

About Me

When not scratching my head for solutions to software challenges, I spend my time playing with my little boy - Michael Jnr.

Quotations

"At 20 years of age the will reigns, at 30 the wit, at 40 the judgment."
Benjamin Franklin

Donate with PayPal - it

Calendar

<<  February 2012  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2005 - 2012

Search