I'm currently using MEF in my project, however, a legacy component uses Castle to export all its components.
I would like to be able to Import from this kernel when creating new objects, in addition to getting the exports from the Xap.
Is this possible? Can you show me some example code?
MEF was designed to be as flexible as possible, and one of its secretly hidden but real nice features, is the ability to define new ExportProvider instances, that allow you to plug in additional components. I've talked about this previously by utilising the Common Service Locator project in an ASP.NET MVC with MEF Project (see part 3 here).
The CSL is a nice flexible approach, as there are many specific CSL implementations for many of the existing IoC containers, such as Castle, Autofac, Ninject, Unity etc.
Another good example can be found here, which demonstrates a slightly different, but fundamentally similar approach.
As Matthew correctly said, the way to do this is using an ExportProvider
Another example is here (it demonstrates exports from Xaml).
Below is what I did in the end to solve the problem.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace MEFCastleBridge
{
public class CastleExportProvider : ExportProvider
{
WindsorContainer _container;
private readonly Dictionary<ExportDefinition, List<Export>> _exports =
new Dictionary<ExportDefinition, List<Export>>();
private readonly object _sync = new object();
public CastleExportProvider(WindsorContainer container)
{
_container = container;
var handlers = _container.Kernel.GetAssignableHandlers(typeof(object));
foreach (var handler in handlers)
{
RegisterCastleComponent(handler);
}
_container.Kernel.ComponentRegistered += ComponentRegistered;
}
protected override IEnumerable<Export> GetExportsCore(
ImportDefinition definition, AtomicComposition atomicComposition)
{
var contractDefinition = definition as ContractBasedImportDefinition;
var retVal = Enumerable.Empty<Export>();
if (contractDefinition != null)
{
string contractName = contractDefinition.ContractName;
if (!string.IsNullOrEmpty(contractName))
{
var exports =
from e in _exports
where string.Compare(e.Key.ContractName, contractName, StringComparison.OrdinalIgnoreCase) == 0
select e.Value;
if (exports.Count() > 0)
{
retVal = exports.First();
}
}
}
return retVal;
}
void RegisterCastleComponent(IHandler handler)
{
var type = handler.Service;
var contractName = type.ToString();
lock (_sync)
{
var found = from e in _exports
where string.Compare(e.Key.ContractName,
contractName, StringComparison.OrdinalIgnoreCase) == 0
select e;
if (found.Count() == 0)
{
var metadata = new Dictionary<string, object>();
var definition = new ExportDefinition(contractName, metadata);
_exports.Add(definition, new List<Export>());
}
var wrapper = new Export(contractName, () => _container.Resolve(type));
found.First().Value.Add(wrapper);
}
}
void ComponentRegistered(string key, IHandler handler)
{
RegisterCastleComponent(handler);
}
}
public interface IMyComponent
{
string TheString { get; }
}
public class RegisteredComponent : IMyComponent
{
public string TheString { get { return "RegisteredComponent"; } }
}
[Export(typeof(IMyComponent))]
public class ExportedComponent : IMyComponent
{
public string TheString { get { return "ExportedComponent"; } }
}
public class ExportExample
{
// Will contain an instance of RegisteredComponent and ExportedComponent
[ImportMany]
public List<IMyComponent> Components { get; set; }
public ExportExample()
{
// Create a Windsor container and add a type.
var container = new WindsorContainer();
container.Register(Component.For<IMyComponent>().ImplementedBy<MyComponent>().LifeStyle.Singleton);
// Add the Export Provider, in addition to the DeploymentCatalog
var compContainer = new CompositionContainer(new DeploymentCatalog(), new CastleExportProvider(container));
// Should only be called once, before any attempt to SatisfyImports.
CompositionHost.Initialize(compContainer);
CompositionInitializer.SatisfyImports(this);
Test = string.Join(", ", Components.Select(c => c.DoSomething));
}
public string Test { get; set; }
}
}
Related
I would like to modify my current LetterFactory implementation and remove the call to Activator.CreateInstance with a call to the container to resolve the current Letter fully initialized with constructor injection. I have read the docs here and here, and even this SO Post while penning this post, but nothing seems to click.
Notes:
1) IDocumentServicesCore is an Aggregate.
2) All Letters are decorated with the LetterTypeAttribute (hundreds of them)
3) This LetterFactory itself is registered in the container.
public class LetterFactory : ILetterFactory
{
private readonly IDocumentServicesCore _documentServicesCore;
public LetterFactory(IDocumentServicesCore documentServicesCore)
{
_documentServicesCore = documentServicesCore;
}
public LetterBase Create(int letterId)
{
if (letterId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(letterId));
}
List<Type> types = typeof(LetterBase).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
.ToList();
LetterBase letter = null;
foreach(Type type in types)
{
LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>().First();
if (!attribute.LetterId.Contains(letterId))
{
continue;
}
letter = Activator.CreateInstance(type, _documentServicesCore) as LetterBase;
break;
}
if (letter != null)
{
return letter;
}
string message = $"Could not find a LetterBase to create for id {letterId}.";
throw new NotSupportedException(message);
}
}
Update1
The problems seems to start with the fact that the letters themselves aren't registered, how to I take the LINQ code that collects the Letters from the assembly and register those enmass?
Thank you,
Stephen
You are looking for IIndex<TKey, TValue> which is a kind of dictionary and it can be composed so IIndex<Int32, Func<LetterBase>> is the type you want.
With such a type your LetterFactory will look like this :
public class LetterFactory : ILetterFactory
{
private readonly IIndex<Int32, Func<LetterBase>> _lettersFactory;
public LetterFactory(IIndex<Int32, Func<LetterBase>> lettersFactory)
{
_lettersFactory = lettersFactory;
}
public LetterBase Create(int letterId)
{
if (letterId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(letterId));
}
Func<LetterBase> letterFactory = null;
if(!this._lettersFactory.tryGetValue(letterId, out letterFactory))
{
string message = $"Could not find a LetterBase to create for id {letterId}.";
throw new NotSupportedException(message);
}
Letter letter = letterFactory();
return letter;
}
}
And then you have to register your types like this :
List<Type> letterTypes = typeof(LetterBase).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
.ToList();
foreach(Type letterType in letterTypes)
{
LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>()
.First();
builder.RegisterType(letterType)
.Keyed<LetterBase>(attribute.LetterId);
}
You will also improve performance with this code : the heavy assembly scanning will only happen once at startup and not for each call.
By the way, be aware of assembly scanning limitation in IIS hosted application : http://autofaccn.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications
You can also rely directly on IIndex<Int32, LetterBase> instead of IIndex<Int32, Func<LetterBase>> it depends on your scope strategy.
You made me do real work, good job :) The following is my solution.
Autofac - Named and Keyed Services - Resolving with Index
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Autofac.Features.Indexed;
public class Program
{
private static IContainer _Container;
public static void Main()
{
InitDependencyInjection();
var rd1 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 1));
rd1.PrintType();
var rd2 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 2));
rd2.PrintType();
}
private static void InitDependencyInjection()
{
var builder = new ContainerBuilder();
var letterTypes = typeof(LetterBase).Assembly.GetTypes()
// Find all types that derice from LetterBase
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
// Make sure they are decorated by attribute
.Where(t =>
t.GetCustomAttributes(typeof(LetterTypeAttribute), false).Length == 1)
.ToList();
//Register with Autofac, Keyed by LetterId
//This should throw an exception if any are duplicated
//You may want to consider using an enum instead
//It's not hard to convert an Int to Enum
foreach(Type letterType in letterTypes)
{
// we already tested the type has the attribute above
var attribute = letterType
.GetCustomAttributes(typeof(LetterTypeAttribute)
, false)[0] as LetterTypeAttribute;
builder.RegisterType(letterType)
.Keyed<LetterBase>(attribute.LetterId);
}
builder.RegisterType<RequiresDependency>();
_Container = builder.Build();
}
}
public class RequiresDependency
{
private readonly LetterBase _letter;
//Autofac automagically provides a factory that returns type
//type you need via indexer
public RequiresDependency(int letterId, IIndex<int, LetterBase> letterFactory)
{
//resolve the needed type based on the index value passed in
_letter = letterFactory[letterId];
}
public void PrintType()
{
Console.WriteLine(_letter.GetType().Name);
}
}
public abstract class LetterBase
{
}
[LetterType(1)]
public class LetterA : LetterBase
{}
[LetterType(2)]
public class LetterB : LetterBase
{}
// make sure the classes using this attribute has only a single attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LetterTypeAttribute : Attribute
{
public LetterTypeAttribute(int letterId)
{
LetterId = letterId;
}
public int LetterId { get; private set; }
}
DotNetFiddle Example
Result:
LetterA
LetterB
We have a lot of registrations within out Unity Container. About 800 lines long in fact.
As Unity does not have a validate method, we introduced a Unit Test to verify the configuration before we test the application which looks like something as follows which works very well and has picked up lots of issues within the configuration:
IUnityContainer container = UnityContainerBuilder.Build();
foreach (ContainerRegistration mapping in container.Registrations)
{
container.Resolve(mapping.RegisteredType, mapping.Name);
}
However, we have lots of classes that follow the decorator pattern in there where we use named registrations. These named registrations are then used to build up the InjectionConstructor for other registrations, for example:
container.RegisterType<IMyType2, Concrete1MyType2>("Concrete1MyType2", new ContainerControlledLifetimeManager());
container.RegisterType<IMyType2, Concrete2MyType2>("Concrete2MyType2", new ContainerControlledLifetimeManager());
container.RegisterType<IMyType1, Concrete1MyType1>(
"Concrete1MyType1",
new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IMyType2>("Concrete2MyType2")));
Is it possible to access through some code what you have configured in the InjectionConstructor's for your registrations?
This article seems to suggest you can.
The example given is:
void DisplayContainerRegistrations(IUnityContainer theContainer)
{
string regName, regType, mapTo, lifetime;
Console.WriteLine("Container has {0} Registrations:",
theContainer.Registrations.Count());
foreach (ContainerRegistration item in theContainer.Registrations)
{
regType = item.RegisteredType.Name;
mapTo = item.MappedToType.Name;
regName = item.Name ?? "[default]";
lifetime = item.LifetimeManagerType.Name;
if (mapTo != regType)
{
mapTo = " -> " + mapTo;
}
else
{
mapTo = string.Empty;
}
lifetime = lifetime.Substring(0, lifetime.Length - "LifetimeManager".Length);
Console.WriteLine("+ {0}{1} '{2}' {3}", regType, mapTo, regName, lifetime);
}
}
OK, after not getting much info on this, I had a look at the internals of Unity as I could not see any public way of doing it. I came up with the following which uses Reflection to access the private members of the various parts of Unity.
The main reasoning for this is that injecting Transient instances into Singletons is a bit of a mismatch. Transient should only last a short time whilst Singletons will probably remain throughout the life of the DI Container. There is some nice information on Simple Injector here. This was one of the reasons for trying to get the Injection information as Unity does not have a verify like Simple Injector.
So, here are my service interfaces and classes I am registering. There are 3 interfaces and 3 concrete implementations with exception to IService2 which has 2 implementations.
public class Service1 : IService1
{
private readonly IService2 _service2;
private readonly IService3 _service3;
public Service1(IService2 service2, IService3 service3)
{
_service2 = service2;
_service3 = service3;
}
public int DoSomethingForService1()
{
return 1;
}
}
public interface IService1
{
int DoSomethingForService1();
}
public class Service2 : IService2
{
public int DoSomethingForService2()
{
return 1;
}
}
public class Service3 : IService3
{
public int DoSomethingForService3()
{
return 1;
}
}
public interface IService3
{
int DoSomethingForService3();
}
public class Service2_1 : IService2
{
public int DoSomethingForService2()
{
return 1;
}
}
public interface IService2
{
int DoSomethingForService2();
}
Now the Unity Container is built up. We tend to use a separate project for this as we don't want all references to be held in the UI. We are using 2 named registrations of IService2.
public static class UnityContainerBuilder
{
public static IUnityContainer BuildDirectInUnity()
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IService2, Service2>("Service2OneAndOnly", new ContainerControlledLifetimeManager());
container.RegisterType<IService2, Service2_1>("Service2_1", new ContainerControlledLifetimeManager());
container.RegisterType<IService3, Service3>(new ContainerControlledLifetimeManager());
container.RegisterType<IService1, Service1>(new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IService2>("Service2OneAndOnly"),
new ResolvedParameter<IService3>()));
}
}
Now the fun part. Here we get the Unity Configuration:
static void Main(string[] args)
{
Console.WriteLine("Building Unity Container...");
var container = (UnityContainer)UnityContainerBuilder.BuildDirectInUnity();
Console.WriteLine("Listing Registrations...");
FieldInfo policiesField = typeof(UnityContainer).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "policies");
FieldInfo parameterValuesField = typeof(SpecifiedConstructorSelectorPolicy).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "parameterValues");
FieldInfo paramNameField = typeof(ResolvedParameter).GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance).First(f => f.Name == "name");
var policies = (PolicyList)policiesField.GetValue(container);
// build up a dictionary for loop below to use to get the lifetime manager
var typeToRegistration = new Dictionary<Tuple<Type, string>, ContainerRegistration>();
foreach (ContainerRegistration registration in container.Registrations)
{
typeToRegistration.Add(new Tuple<Type, string>(registration.RegisteredType, registration.Name), registration);
}
// now output the list
foreach (ContainerRegistration registration in container.Registrations)
{
Console.WriteLine("{0} to {1}, {2}, {3}",
registration.RegisteredType.Name,
registration.MappedToType.Name,
registration.Name ?? "[default]",
registration.LifetimeManagerType.Name);
// need to check for our InjectionConstructor - I need local = false
IConstructorSelectorPolicy constructorPolicy = policies.Get<IConstructorSelectorPolicy>(
new NamedTypeBuildKey(registration.MappedToType, registration.Name), false);
// and I need SpecifiedConstructorSelectorPolicy as we are not using the default constructor
if (constructorPolicy is SpecifiedConstructorSelectorPolicy)
{
var specifiedConstructorPolicy = constructorPolicy as SpecifiedConstructorSelectorPolicy;
// now output the ResolvedParameters for type, name, lifetime manager
var paramValues = (InjectionParameterValue[])parameterValuesField.GetValue(specifiedConstructorPolicy);
foreach (var param in paramValues)
{
if (param is ResolvedParameter)
{
var resolvedParam = param as ResolvedParameter;
var name = (string)paramNameField.GetValue(resolvedParam);
string lifeTimeManagerName =
typeToRegistration[new Tuple<Type, string>(resolvedParam.ParameterType, name)].LifetimeManagerType.Name;
Console.WriteLine("\t{0}, {1}, {2}", param.ParameterTypeName, name ?? "[default]", lifeTimeManagerName);
}
else
{
Console.WriteLine("\t{0}", param.ParameterTypeName);
}
}
}
}
Console.WriteLine("Complete");
Console.ReadLine();
}
The drawbacks of this is that it is purely reflection based hacking and that it does not support the InjectionFactory where you can configure a new instance of a type.
I am trying to make a (my first MEF) system in which plugins can be recursive, i.e. my main system calls a MEF plugin with a standard interface, which on its own can then call another (or several) plugin(s), and so on.
When testing though, my plugin does not call the underlying plugin, but starts processing itself (creating a loop).
Any idea how I can prevent this?
Interface:
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
}
My main plugin inherits the interface, and defines the import for the next (The subplugin has the same definition):
[Export(typeof(IConnector))]
public class Connector : IConnector
{
[Import(typeof(IConnector))]
private IConnector connector;
....
The called plugin is initiated (in the Run method of the main plugin):
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
string calledConnector = Path.Combine(AssemblyDirectory, "subplugin.dll");
AssemblyCatalog assembyCatalog = new AssemblyCatalog(Assembly.LoadFrom(calledConnector));
CompositionContainer container = new CompositionContainer(assembyCatalog);
container.ComposeParts(this);
....
The container should now contain just one plugin, the subplugin.dll.
I call the method 'Run' which is in the interface to invoke the subplugin method:
XDocument something = connector.Run(serviceCredentials, connectorids, connectorkeys);
But, instead of running the subplugin code, the 'Run' method in my main plugin activates, which keeps activating itself.
When I remove the [Export(typeof(iConnector)] in the main plugin, the subplugin is activated, but I want my main plugin to be able to be called in the same manner.
Being new to MEF I am stuck as to how to solve this. Any help would be much appreciated!
You should use Contracts and specify your intent, otherwise MEF will go into an infinite loop or pick the Connector as it exposes IConnector itself.
Some more info from MSDN.
For example
[Export("Container", typeof(IConnector))]
public class Connector : IConnector
{
[Import("Component", typeof(IConnector))]
private IConnector connector;
....
UPDATE
So after giving it some thought, here is an example of metadata based approach, and one that also limits the number of expensive catalog operations.
The IConnector
using System.Xml.Linq;
namespace Common
{
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
void Identify();
}
}
The metadata attribute ConnectorMetadata
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Common
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ConnectorMetadata : ExportAttribute
{
public string Name { get; private set; }
public ConnectorMetadata(string name):base(typeof(IConnector))
{
Name = name;
}
public ConnectorMetadata(IDictionary<string, object> metaData) : base(typeof (IConnector))
{
Name = Convert.ToString(metaData["Name"]);
}
}
}
The lazy singleton for PluginsCatalog
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using Common;
namespace Common
{
public class PluginsCatalog
{
[ImportMany]
public Lazy<IConnector, ConnectorMetadata>[] Connectors;
private static readonly Lazy<PluginsCatalog> LazyInstance = new Lazy<PluginsCatalog>(() => new PluginsCatalog());
private PluginsCatalog()
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Directory.GetCurrentDirectory();
var directoryCatalog = new DirectoryCatalog(path, "*plugin.dll");
var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(aggregateCatalog);
container.SatisfyImportsOnce(this);
}
public static PluginsCatalog Instance { get { return LazyInstance.Value; } }
public IConnector GetConnector(string name)
{
var match = Connectors.SingleOrDefault(s => s.Metadata.Name.Equals(name));
return match == null ? null : match.Value;
}
}
}
The "Primary" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace Common
{
[ConnectorMetadata("Primary")]
public class Connector : IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
PluginsCatalog.Instance.GetConnector("Sub").Identify();
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
The "Sub" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace SubPlugin
{
[ConnectorMetadata("Sub")]
public class SubConnector:IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
and finally the program itself:
namespace SOMEF
{
class Program
{
static void Main(string[] args)
{
var connector = PluginsCatalog.Instance.GetConnector("Primary");
connector.Identify();
connector.Run(null, null, null);
}
}
}
Which prints:
SOMEF.Connector
SubPlugin.SubConnector
Hope this helps ... :)
You might want to read this https://msdn.microsoft.com/en-us/library/ee155691(v=vs.110).aspx
With exmplanation how named exports are used
public class MyClass
{
[Import("MajorRevision")]
public int MajorRevision { get; set; }
}
public class MyExportClass
{
[Export("MajorRevision")] //This one will match.
public int MajorRevision = 4;
[Export("MinorRevision")]
public int MinorRevision = 16;
}
I have a class to test which is tricky to test using Rhinomock unlike normal classes bacause its constructor is injected with a dependency which is not a single interface but an array of Interface objects. Please help me set up all stuff to write a test using rhinomock.
namespace ClinicalAdvantage.Domain.UserAppSettings
{
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
public class Agg : IAgg
{
private readonly ISource[] sources;
public Agg(ISource[] sources)
{
this.sources = sources;
}
public JObject GetAll()
{
var obj = new JObject();
foreach (var source in this.sources)
{
var token = source.GetCurr();
if (token != null)
{
obj.Add(new JProperty(source.Name, token));
}
}
return obj;
}
}
ISource is an interface which has 2 implementations. GetALL() iterates thro each implementated class object and calls the GetCurr method in each of the object and aggregates the result. I have to stub GetCurr method to return a standard Jtoken. I am unable to create a mock of this class Agg or a stub of ISource.
public interface ISource
{
string Name { get; }
bool Enabled { get; }
JToken GetCurr();
}
}
Something like this might work:
[TestClass]
public class AggTest
{
private ISource Isource;
private Agg agg;
[TestInitialize]
public void SetUp()
{
Isource = MockRepository.GenerateMock<ISource>();
agg = new Agg(new [Isource]);
}
[TestMethod]
public void GetAll()
{
Isource.Stub(x => x.GetCurr()).
Return(new JToken());
var jObject = agg.GetAll();
Assert.IsNotNull(jObject);
// Do your assertion that all JProperty objects are in the jObject
// I don't know the syntax
}
}
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();
}
}
}