2
votes

In my application, I want to take dependencies on multiple repositories in a class, where not all of them are required each time. Rather than constructing an instance of each one where unnecessary, I use the Typed Factory facility in Windsor.

However, registering a factory for each repository is a bit tiresome, and I would like to replace this with an open generic registration. What I want to do is something like the following:

container.Register(
    Component.For<IFactory<IRepository<>>>().AsFactory()
);

However, this is a syntax error because of the missing type parameter for IRepository. Is there a syntax I can use which would make this work?

NB: I'm aware that I can register an untyped Factory interface and use this to create multiple components. I'm not interested in doing this as this is essentially taking a dependency on a service locator - if I've not registered a dependency then I won't know about it until the code tries to use it - with my approach I know about this in the constructor even though I'm not creating an instance yet.

Full (simplified) sample below:

public class TestA { }
public class TestB { }
public interface IRepository<T> { T Create();    }
public class Repository<T> : IRepository<T>
{
    public T Create() { return Activator.CreateInstance<T>(); }
}

public interface IFactory<T>
{
    T Create();
    void Release(T instance);
}

class Program
{
    static void Main(string[] args)
    {
        IWindsorContainer container = new WindsorContainer();
        container.AddFacility<TypedFactoryFacility>();

        container.Register(
            // Individual registrations of repositories here are fine
            Component.For<IRepository<TestA>>().ImplementedBy<Repository<TestA>>(),
            Component.For<IRepository<TestB>>().ImplementedBy<Repository<TestB>>()
        );

        container.Register(
            // Individual registrations of factories - works, but trying to avoid!
            Component.For<IFactory<IRepository<TestA>>>().AsFactory(),
            Component.For<IFactory<IRepository<TestB>>>().AsFactory()
        );

        container.Register(
            // Generic Registration of Factories - syntax errors
            // Component.For<IFactory<IRepository<>>>().AsFactory()
            // Component.For(typeof(IFactory<IRepository<>>)).AsFactory()
        );

        var factoryA = container.Resolve<IFactory<IRepository<TestA>>>();
        var factoryB = container.Resolve<IFactory<IRepository<TestB>>>();

        var repoA = factoryA.Create();
        var repoB = factoryB.Create();

        Console.WriteLine("Everything worked");
    }
}
2

2 Answers

5
votes

Your factory inteface definition is a little too "open". Change your factory interface as follows:

public interface IRepositoryFactory<T>
{
    IRepository<T> Create();
    void Release(IRepository<T> instance);
}

And you can then register:

container.Register(Component.For(typeof(IRepositoryFactory<>)).AsFactory());

And resolve:

var factoryA = container.Resolve<IRepositoryFactory<TestA>>();
var factoryB = container.Resolve<IRepositoryFactory<TestB>>();
1
votes

There's a pattern for grouping repositories together. It is called unit of work. So, instead of creating a factory for creating repositories, create a unit of work class that references these repositories. For instance:

public abstract class UnitOfWork : IDisposable
{
    // here is your factory
    protected abstract IRepository<T> GetRepository<T>();

    public IRepository<User> Users
    {
        get { return this.GetRepository<User>();
    }

    public IRepository<Customer> Customers
    {
        get { return this.GetRepository<Customer>();
    }

    // etc..
}

In your Composition Root you can define an UnitOfWork implementation that holds a reference to Windsor and enables you to get IRepository<T> implementations:

internal sealed class WindsorUnitOfWork : UnitOfWork
{
    private WindsorContainer container;

    public WindsorUnitOfWork(WindsorContainer container)
    {
        this.container = container;
    }

    protected override IRepository<T> GetRepository<T>()
    {
        return this.container.Resolve<IRepository<T>>();
    }
}

And register it as follows:

container.Register(Component.For<UnitOfWork>()
    .ImplementedBy<WindsorUnitOfWork>()
        .LifeStyle.Transient);

Consumers now have a really convenient way of using the repositories:

private readonly UnitOfWork db;

public KarmaService(UnitOfWork db)
{
    this.db = db;
}

public int CalculateKarmaForActiveUsersByName(string name)
{
    var users =
        from user in this.db.Users
        where user.Name == name
        where user.Active
        select user;

    return users.Sum(user => user.Karma);
}