Tuesday, June 10, 2008

Windsor and Linq DataContexts

For one of the systems I built I needed to use the Windsor container to resolve instances of a Linq DataContext. The DataContext was created using the standard Linq dbml designer. Using standard Windsor stuff, the configuration should look something like this:

<properties>
<connectionString>
Data Source=server;
Initial Catalog=Sample;
Integrated Security=True
</connectionString>
</properties>

...

<component
id="datacontext"
service="Sample.SampleDataContext, Sample"
type="Sample.SampleDataContext, Sample"
lifestyle="transient">
<parameters>
<connection>#{connectionString}</connection>
</parameters>
</component>

Easy, right? Unfortunately if you try to run this and do container.Resolve<SampleDataContext>(); you'll get an error like: "Key invalid for parameter connection." It turns out that what is happening is that Windsor doesn't know which Constructor to use. The generated DataContext has 2 single parameter constructors, one that takes a string named "connection", and the other that takes an IDbConnection named "connection". I think what happens is that the container tries to convert the property to an IDbConnection and it fails.

What we need to do is change the way the constructor is chosen. One simple way is just to add another constructor to your DataContext with a dummy ignored parameter. Like this:

public SampleDataContext(string connection, int ignoreThis)

If you then add <ignoreThis>5</ignoreThis> to the parameters section in the config then Windsor will find your new constructor and everything will work. This, however, is ugly.

A better way is to create a Facility. What we're going to do is assume that all our DataContexts will follow the same pattern, i.e., that we always want to use the constructor that takes a single string parameter.

First, we create a facility that inherits from Castle.MicroKernel.Facilities.AbstractFacility. We want to replace the default ComponentActivator, so we'll need to get ahold of the ComponentModel when it's created. We can override Init like this:

protected override void Init()
{
Kernel.ComponentModelCreated +=
(model) =>
{
if (typeof(System.Data.Linq.DataContext)
.IsAssignableFrom(model.Implementation))
{
model.CustomComponentActivator = typeof(CustomActivator);
}
};
}

Notice we're only setting a custom activator for classes that inherit from DataContext. Now all that's left is to implement CustomActivator. Luckily, the default activator is designed to be inherited from; everything we need to change is virtual. Create CustomActivator that inherits from Castle.MicroKernel.ComponentActivator.DefaultComponentActivator. You'll need a constructor that takes the same parameters. All we need to do is override SelectEligibleConstructor to force the container to choose the single string parameter one. We can do that just like this:

protected override
ConstructorCandidate SelectEligibleConstructor
(CreationContext context)
{
return
Model.Constructors.Cast<ConstructorCandidate>()
.First(c => c.Constructor.GetParameters().Length == 1
&& c.Constructor.GetParameters()[0]
.ParameterType == typeof(string));
}

The final step is just to add the facility to our configuration.

<facilities>
<facility id="linqFacility" type="Core.LinqFacility, Core" />
</facilities>

Now everything works as expected for all DataContexts in our config.

1 comment:

Carl Johansen said...

Brilliant - just what I was looking for, thanks!