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; }
}
From the code, that method goes via:
public static IEnumerable<TResult> ExecuteSqlStringAccessor<TResult>(this Database database, string sqlString)
where TResult : new()
{
return CreateSqlStringAccessor<TResult>(database, sqlString).Execute();
}
then to
IRowMapper<TResult> defaultRowMapper = MapBuilder<TResult>.BuildAllProperties();
which goes via
return MapAllProperties().Build();
which is:
public static IMapBuilderContext<TResult> MapAllProperties()
{
IMapBuilderContext<TResult> context = new MapBuilderContext();
var properties =
from property in typeof(TResult).GetProperties(BindingFlags.Instance | BindingFlags.Public)
where IsAutoMappableProperty(property)
select property;
foreach (var property in properties)
{
context = context.MapByName(property);
}
return context;
}
so no; I see no evidence of any caching there. You could add some, or you could use domething that already does materializer and parameterization caching (*cough* dapper-dot-net *cough*)
Here is an easy and nice hack suggested by entlib support team (you can check the full thread at http://entlib.codeplex.com/discussions/281833):
randylevy Mon 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 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);
UPDATE From EntLib again (an even better solution):
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();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With