Implementing an IDbSet to support caching with Entity Framework 4.1

Share on Facebook

Using generic Repositories (Repository<TEntity>) with your Data Access Layer (DAL) provides an elegant approach to isolating your DAL/ORM (Object Relational Mapper) technology from your upper layers such as the Business logic and the UI.

Entity Framework 4.1 code first in particular lends itself to the repository pattern and in particular the generic repository pattern.

However when working with RIA Services, the client side code generated by the RIA services calls back into the server side RIA services with expected service endpoints. These end points are dificult to map with a generic repository in the server side RIA services.

With respect to a silverlight RIA application I am currently working on, I needed to implement some entity level auditing and also server side caching. Since the repository pattern was not working well with RIA services, I hooked up the auditing as an interceptor before the actual DbContext.SaveChanges method call. The approach used is very well documented in this post.

Next was caching, I decided to implement an IDbSet by using the decorator pattern. I figured the implementation would replace the DBSet<TEntity> which maintains the various entity sets in my DbContext derived context class.

My current implementation looks like so:

   
public class CachedDbSet : IDbSet, IOrderedQueryable, IOrderedQueryable, IQueryable, IQueryable, IEnumerable, IEnumerable, IListSource  where TEntity : class
{
    private readonly DbSet Set;
    private IQueryable CachedSet;

    private readonly ICacheProvider cacheProvider = new CacheProvider();
    private int cacheDurationInSecs = 3600;

    public CachedDbSet(DbContext context) : this(context, "")
    {
    }

    public CachedDbSet(DbContext context, string includeProperty)
    {
        Set = context.Set();
        
        if (!String.IsNullOrEmpty(includeProperty))
        {
            CachedSet = context.Set().Include(includeProperty);
        }
        else 
        {
            CachedSet = Set;
        }
    }    

    #region cached sets
    
    private IQueryable All
    {
        get
        {
            InvalidateCache();
            var key = typeof(TEntity).ToString();
            return cacheProvider.GetOrStore>(key, () => CachedSet, cacheDurationInSecs);
        }
    }

    #endregion

    public Func MatchesFilter { get; private set; }    

    public TEntity Add(TEntity entity)
    {
        InvalidateCache();
        return Set.Add(entity);
    }

    public TEntity Attach(TEntity entity)
    {
        InvalidateCache();
        return Set.Attach(entity);
    }

    public TDerivedEntity Create() where TDerivedEntity : class, TEntity
    {
        var entity = Set.Create();
        return (TDerivedEntity)entity;
    }

    public TEntity Create()
    {
        var entity = Set.Create();
        return entity;
    }

    public TEntity Find(params object[] keyValues)
    {
        var entity = ((DbSet)All).Find(keyValues);
        if (entity == null)
        return null;
        return entity;
    }

    public TEntity Remove(TEntity entity)
    {
        InvalidateCache();
        return Set.Remove(entity);
    }

    public ObservableCollection Local { get { return Set.Local; } }

    IEnumerator IEnumerable.GetEnumerator() { return Set.AsEnumerable().GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator() { return Set.AsEnumerable().GetEnumerator(); }

    Type IQueryable.ElementType { get { return typeof(TEntity); } }

    Expression IQueryable.Expression { get { return Set.AsQueryable().Expression; } }

    IQueryProvider IQueryable.Provider { get { return Set.AsQueryable().Provider; } }

    bool IListSource.ContainsListCollection { get { return false; } }

    IList IListSource.GetList() { throw new InvalidOperationException(); }   

    public void AttachAsModified(TEntity entity, TEntity original, DbContext context)
    {
        InvalidateCache();
        Set.AttachAsModified(entity, original, context);
    }

    public IQueryable Include(string propertyName)
    {
        return All.Include(propertyName);
    }

    private void InvalidateCache()
    {
        cacheProvider.Invalidate(typeof(TEntity).ToString());
    }
}

The above CachedDbSet<TEntity> may then be used in the Entity Framework 4.1 derive DbContext class like so:

   
private CachedDbSet projects;
public CachedDbSet Projects
{
    get
    {
        if (projects == null)
            projects = new CachedDbSet(this, "Phases.PhaseType");
        return projects;
    }
    set
    {
        projects = value;
    }
}

Where Project is an entity in the DB.

Unfortunately, I have hit a brick wall in the sense that, the context used to generate the
wrapped DbSet in the constructor is disposed before RIA services tries to make any updates, so
the AttachAsModified inner call on the AttachAsModified method fails. I have tried for over an
hour to find an alternative with no joy.

If you have a way around this, please let me know by posting a comment. I have engaged the CachedDbSet implementation but you will notice I invalidate the cache before checking it on the "All" property. So effectively short circuiting it.

Next to implement is Remote Logging with the new Enterprise Library Silverlight Integration pack. Stay tuned.

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