Dailycode.info

Short solution for short problems

EntityFramework: Adding IEnumerable to Cache.

My collegues were rewriting some code. This involved generic caching of default mapping objects. 

The mapper loops over a collection of database objects and maps them to business objects. Using seperate mapping tables in a seperate database, we had to map them in the loop. But we didn't want to get all the mappings from the database, instead cache them. 

When we looped over 870 objects and mapped them without the extra mappings, it finished in less then a second. When using the mapping to the database, it took about 2 minutes. Then we rewrote to use caching, but the first time the objects were filled in the cache, it still took 2 minutes. 

The code where the caching of the mappings was looked like this:

        private IEnumerable<TResult> GetMappings<TResult, TEntity>(bool refreshCache = false, Func<TEntity, TResult> mappingFunction = null)
            where TResult : StandardMapping, new()
            where TEntity : SQL.Mappings.StandardMapping, new()
        {
            var key = typeof(TResult).FullName.ToLower();
            if (refreshCache)
                CacheHelper.Remove(key);
            return CacheHelper.Get(key, () =>
            {
                var map = mappingFunction ?? GeneralMapper.FromSQL2BO<TResult>;
                return MasterDl.GetMappings<TEntity>().Select(m => map(m));//returning IEnumerable
            });
        }

The CacheHelper.Get would add the result if it was not cached yet. 

        public static T Get<T>(string key, Func<T> cacheMissCallback)
        {
            if (MemoryCache.Default.Contains(key))
            {
                return Get<T>(key);
            }
            else
            {
                var value = cacheMissCallback();
                Set(key, value);
                return value;
            }
        }

But still it took the same amount of time to load.

The problem was that the Enumerator was cached and not the result. So after I added a .ToList() that made sure the data was fetched from the DB , the delay was completely gone:

        private IEnumerable<TResult> GetMappings<TResult, TEntity>(bool refreshCache = false, Func<TEntity, TResult> mappingFunction = null)
            where TResult : StandardMapping, new()
            where TEntity : SQL.Mappings.StandardMapping, new()
        {
            var key = typeof(TResult).FullName.ToLower();
            if (refreshCache)
                CacheHelper.Remove(key);
            return CacheHelper.Get(key, () =>
            {
                var map = mappingFunction ?? GeneralMapper.FromSQL2BO<TResult>;
                return MasterDl.GetMappings<TEntity>().ToList().Select(m => map(m));//Tolist executes the query before returning an IEnumerable
            });
        }

Using an SQL profiler is a big help in these king of problems.

Here the result of the cached enumerator:


Here with the .ToList() added to resolve the Enumerator



Combining self-tracking entities context with a Generic Singleton class. (Caching and/or refreshing)

This post is because I was using a generic singleton to instantiate the context of the entity framework. This resulted in a good chaching framework. All requests were enjoying this cached framework and passing changes to each other. But then some data was changed by an external application, even some data manipulated directly on the DB. The website did not reacht to these results, unless you would recycle the applicatin pool and release the generic singleton instance. So we came up wih a solution combining the strengt of this caching principle and still react to changes outside of the context.

We have several process layer classes in which we relate several data. For example for all master data I have a class MasterDataPL.cs: this class instantiates the context of the entity framework. Since its more or less static data, we can fully use the single instance. I also have a class with production data. The difference here is in the instantiating. Since this data changes a lot, I need to get a new instance of the context before every selection of data. The data can be manipulated from outside of the program, even directly in the database. To reflect these changes immediately, we cannot cache the data.

In the master data class, I will instantiate the context only once and use this instance as long as the process lasts:

private MyProjectEntities.Entities ents;

 

public MyProjectEntities.Entities StaticEnts

{

    get

    {

        if (ents == null)

        {

            ents = new MyProjectEntities.Entities(ConnectionString.ToString());

        }

        return ents;

    }

}

 

To get static data I just use the property accessor  StaticEnts. Also insert, update and delete actions will use this StaticEnts property.  Since this data is not manipulated from outside frequently (because the data is still refreshed after a recycle of the app pool).

Get data

public List<Users> GetAllUsers()

{

    return StaticEnts. Users.OrderBy(u => u.LastName).ThenBy(u => u.FirstName).ToList();

}

 

Update data

public void SaveProject(Projects project)

{

    try

    {

        project.Modified = DateTime.Now;

        StaticEnts.Projects.ApplyChanges(project);

        StaticEnts.SaveChanges();

    }

    catch (Exception er)

    {

        StaticEnts.Refresh(RefreshMode.StoreWins, project);

        throw new ArgumentException("Save failed, please check your data!", er.InnerException);

    }

}

 

Now, for the variable data, we still use the static property accessor for all insert, update and delete. But for the selection of data, we use a new instance of the context. Resulting in 2 properties, that we can use depending on the data:

private MyProjectEntities.Entities ents;

 

public MyProjectEntities.Entities Ents

{

    get

    {

        ents = new MyProjectEntities.Entities(ConnectionString.ToString());

        return ents;

    }

}

 

public MyProjectEntities.Entities StaticEnts

{

    get

    {

        if (ents == null)

        {

            ents = new MyProjectEntities.Entities(ConnectionString.ToString());

        }

        return ents;

    }

}

 

So to get data, it simply looks like this:

public ProjectDomains GetDomain(int domainID)

{

    return Ents.ProjectDomains.Where(pd => pd.DomainID == domainID).FirstOrDefault();

}

 

This will get the latest data from the database.  This way we minimalize the delays for getting data from and sending data to the database.

The caching now happens using the generic singleton pattern:

GenericSingleton<MYProjectsPL.ProjectsPL>.GetInstance().GetAllProjects(userID);