does enterprise library 5.0 cache mappings between datareader and custom classes for accessor methods

Topics: Data Access Application Block
Dec 5, 2011 at 1:00 PM

Wanted to know if the Accessor methods of Enterprise Library 5.0 cache the fields of datareader as well as custom classes for performance such that it does not look up field names on custom classes using reflections and does not look up field names on datareader when mapping datareader to objects? Because its a pretty expensive operation to map custom class fields to datareader fields for every access / code block

public partial class _Default : System.Web.UI.Page
{
   
protected void Page_Load(object sender, EventArgs e)
   
{
       
Database db = EnterpriseLibraryContainer.Current.GetInstance<Database>();
       
var r = db.ExecuteSqlStringAccessor<Region>("SELECT * FROM Region");
   
}

}

public class Region
{
   
public string RegionnId { get; set; }
   
public string Name { get; set; }
}
Dec 5, 2011 at 11:39 PM

No, there isn't any caching of the RowMapper.  The only caching I'm aware of for the Data Access Application Block is stored procedure parameter caching.

If you are using the default mapper then you could cache the results yourself and pass into the ExecuteSqlStringAccessor method since it supports IRowMapper and IResultSetMapper overloads.

E.g.:

    public class RowMapperCache
    {
        private Dictionary<Type, object> cache = new Dictionary<Type, object>();
        private object locker = new object();

        public IRowMapper<T> GetCachedMapper<T>() where T : new()
        {
            Type type = typeof(T);

            lock (locker)
            {
                if (!Contains(type))
                {
                    cache[type] = MapBuilder<T>.BuildAllProperties();
                }
            }

            return cache[type] as IRowMapper<T>;
        }

        private bool Contains<T>(T type) where T : Type
        {
            return cache.ContainsKey(type);
        }
    }
     // retrieve default mapper and cache it
     IRowMapper<Region> regionMapper = rowMapperCache.GetCachedMapper<Region>();

     var db = EnterpriseLibraryContainer.Current.GetInstance<Database>();
     var r = db.ExecuteSqlStringAccessor<Region>("SELECT * FROM Region", regionMapper);
   

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Dec 6, 2011 at 8:29 AM

super answer randy! thanks a lot ;-) wish entlib 5 had this option out of the box :-( but this would work for now :-)

Dec 6, 2011 at 11:17 PM
Edited Dec 8, 2011 at 12:42 AM

Thanks.  Maybe, it can be put on the table for Enterprise Library 6 since it seems like a good idea?

Just for fun, I refined the example a bit to store the RowMapperCache as a singleton inside of the EnterpriseLibraryContainer so that
it can be retrieved similar to other Enterprise Library objects.  Although not an Enterprise Library "native" class, the RowMapperCache is used
only with Enterprise Library so it's not a huge leap to store it in the container (especially if you aren't using full Unity IoC).

using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ContainerModel;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ContainerModel.Unity;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Unity;

namespace RowMapperConsole 
{
    public class Region {}
 
    public class RowMapperCache
    { 
        private Dictionary<Type, object> cache = new Dictionary<Type, object>();
        private object locker = new object();

        public IRowMapper<T> GetCachedMapper<T>() where T : new()
        {
            Type type = typeof(T);
           
	    lock (locker)
            {
                if (!Contains(type))
                {
                    cache[type] = MapBuilder<T>.BuildAllProperties();
                }
            }

            return cache[type] as IRowMapper<T>;
        }

        private bool Contains(T type)
        {
            return cache.ContainsKey(type);
        } 
    }

    class Program
    {
        static void Main(string[] args)
        {
            ApplicationInitialize();

            // ...

            IEnumerable<Region> regions = GetRegions();
        }

        public static void ApplicationInitialize()
        {
            ConfigureContainer(container =>
            {
                // Register as Singleton
                container.RegisterType<RowMapperCache>(new ContainerControlledLifetimeManager());
            });
        }

        public static void ConfigureContainer(Action<IUnityContainer> action)
        {
            IUnityContainer container = new UnityContainer();
 
            if (action != null)
                action(container);

            IContainerConfigurator configurator = new UnityContainerConfigurator(container);
            EnterpriseLibraryContainer.ConfigureContainer(configurator, ConfigurationSourceFactory.Create());
            IServiceLocator locator = new UnityServiceLocator(container);
            EnterpriseLibraryContainer.Current = locator;
        }

        public static IEnumerable<Region> GetRegions()
        {
            IRowMapper<Region> regionMapper = EnterpriseLibraryContainer.Current.GetInstance<RowMapperCache>()
                                                  .GetCachedMapper<Region>();
            var db = EnterpriseLibraryContainer.Current.GetInstance<Database>();
 
            return db.ExecuteSqlStringAccessor<Region>("SELECT * FROM Region", regionMapper).ToList();
        }
    }
}

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Dec 7, 2011 at 9:07 AM

randy - you absolutely rock mate - you should definitely have it inside entlib 6.0 or even earlier (via some interim update if possible)

i was a bit surprised when i discovered entlib 5.0 does not cache the mappings - i am using entlib 5.0 to read close to 18.5 million recs - to use it without some sort of cache for mappings would be a little wild

not sure how wild this suggestion is - but it would be good to make use of the entlib caching block for this - which could possibly open up the options to cache these mappings to file local memory / azure app fabric cache / memcache / etc etc

would be very useful when running stuff in the cloud

Dec 8, 2011 at 12:42 AM

Thanks for the kind words.

Just curious if you have benchmarked the performance improvement for caching?

Using the caching block is an interesting idea - it would certainly add flexibility and I believe that, in general, it's a good idea to eat your own dog food. However, in this case I would say that an internal caching mechanism would be a better fit.  This follows the same model as stored procedure parameter caching.  Also, using the caching block complicates the configuration.  If I'm using the data access block I would prefer that it "just work" instead of introducing a dependency on configuring the caching block.  Sure you could build in logic to use an internal cache but allow the block to be configured to use a cache of the user's choosing but that does introduce complexity: development and end user configuration.

I also know a lot of questions that I see are when multiple blocks work together.  e.g. Exception Handling Block and Logging Block, Logging Block and Data Access Block.

Also, bear in mind that I'm speaking for myself here and I don't represent the views of the Development Team or Microsoft.  :)

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Dec 8, 2011 at 8:47 PM

The caching block wouldn't work in general for this purpose - the mappers are generated code, not data, and therefore aren't serializable in any meaningful short of writing out a dynamically generated assembly then trying to reload it. Not a pleasant thing to try and do.

The goal when the team built it (I was on the team at the time, not any longer) was to be an 80% solution. We deliberately decided to go for simple. Part of that was passing off to the user the responsibility for holding on to and reusing the mapper objects.

Not saying the team couldn't grow the feature in future versions, just trying to fill in a little of the history.

 

 

Dec 8, 2011 at 11:55 PM

Chris, that's a good point about serialization.  I hadn't considered it.  I was thinking along the lines of an in memory cache but (the potential for) serialization
does introduce issues.

Thanks for filling in the history and thought process -- it's much appreciated.  

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com

Dec 10, 2011 at 3:58 PM
Edited Dec 10, 2011 at 10:40 PM

@xtremebiz, I was thinking about your comment to add the caching to Enterprise Library and my first thought was that it probably won't be happening through an optional update.

My second thought was that we could do it ourselves (in a manner)!

Just create an extension method:

    public static class DatabaseExtensions
    {
        public static IEnumerable<T> ExecuteSqlStringAccessor<T>(this Database database, string sqlString) where T : new()
        {
            RowMapperCache mapperCache = EnterpriseLibraryContainer.Current.GetInstance<RowMapperCache>();           
            IRowMapper<T> mapper = mapperCache.GetCachedMapper<T>();

            return database.ExecuteSqlStringAccessor<T>(sqlString, mapper); 
        }
    }

 Now, the data access code looks just like "vanilla" Enterprise Library data access code except that we are using our extension method with caching.  

   public static IEnumerable<Region> GetRegions()
   {
       var db = EnterpriseLibraryContainer.Current.GetInstance<Database>();
       return db.ExecuteSqlStringAccessor<Region>("SELECT * FROM Region").ToList();
   }

If you want to revert to the base functionality (or if the caching functionality is included in a future release) then just remove the new extension method
and the Enterprise Library extension method will be called without any changes to the data access code at all.

If there are any issues with resolving between the new extension method and the Enterprise Library extension method then see this answer on Stack Overflow.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com