views:

54

answers:

2

If I have the following setup, how can I configure my container to use the same database, when objects are created in the same context

public class Database { }
public interface IRepository { Database Database { get; } }
public interface IFooRepository : IRepository { }
public interface IBarRepository : IRepository { }

public class FooRepository : IFooRepository
{
    public Database Database { get; private set; }
    public FooRepository(Database database) { this.Database = database; }
}

public class BarRepository : IBarRepository
{
    public Database Database { get; private set; }
    public BarRepository(Database database) { this.Database = database; }
}

public class Consumer
{
    public IFooRepository fooRepository { get; private set; }
    public IBarRepository barRepository { get; private set; }
    public Consumer(IFooRepository fooRepository, IBarRepository barRepository)
    {
        this.fooRepository = fooRepository;
        this.barRepository = barRepository;
    }
}

[TestClass]
public class ConfigurationTest
{
    private IWindsorContainer container;

    [TestMethod]
    public void SameDatabaseIsUsed()
    {
        Consumer consumer = container.Resolve<Consumer>();
        IFooRepository fooRepository = consumer.fooRepository;
        IBarRepository barRepository = consumer.barRepository;
        Assert.AreEqual(fooRepository.Database, barRepository.Database); //FAILS
    }

    [TestMethod]
    public void DifferentDatabaseIsUsed()
    {
        Consumer consumer = container.Resolve<Consumer>();
        IFooRepository fooRepository = consumer.fooRepository;
        Consumer consumer2 = container.Resolve<Consumer>();
        IBarRepository barRepository = consumer2.barRepository;
        Assert.AreNotEqual(fooRepository.Database, barRepository.Database); //PASSES
    }

    [TestInitialize]
    public void SetUp()
    {
        container = new WindsorContainer();
        container.Register(
            Component.For<Database>().ImplementedBy<Database>().LifeStyle.Transient,
            AllTypes.FromThisAssembly()
                    .BasedOn<IRepository>().WithService.FromInterface()
                    .Configure(c => c.LifeStyle.Transient),
            Component.For<Consumer>().ImplementedBy<Consumer>().LifeStyle.Transient
                    );

    }
}

EDIT: I have tried to use a custom lifestyle, but i cannot figure out what I can use to detect that i have switched context

public class DatabaseLifestyleManager : AbstractLifestyleManager
{
    private CreationContext context;
    private Database database;

    private Database Database
    {
        get
        {
            if (database == null) database = new Database();
            return database;
        }
        set 
        {
            database = null;
        }
    }

    public override object Resolve(CreationContext context)
    {
        if (this.context!=null && this.context.??? == context.???)
            return Database;
        else
        {
            this.context = context;
            Database = null;
            return Database;
        }

    }

    public override void Dispose()
    {
        database = null;
        context = null;
    }
}
......
Component.For<Database>().ImplementedBy<Database>().LifeStyle.Custom(typeof(DatabaseLifestyleManager)
A: 

I came up with this solution myself by implementing IDisposable, so that I can use a kind of sessionscope for the Database

Would this be a valid way to handle this situation?

All test passes, but there is some added functionality, that must be implemented in all my future consumers of the repositories:

public class Database { }
public interface IRepository : IDisposable { Database Database { get; } }
public interface IFooRepository : IRepository { }
public interface IBarRepository : IRepository { }

public abstract class BaseRepository : IDisposable
{
    public BaseRepository(Database database) { this.Database = database; }
    public Database Database { get; private set; }
    public void Dispose() { Database = null; }
}

public class FooRepository : BaseRepository, IFooRepository
{
    public FooRepository(Database database) : base(database) { }
}

public class BarRepository : BaseRepository, IBarRepository
{
    public BarRepository(Database database) : base(database) { }
}

public abstract class BaseConsumer : IDisposable
{
    public abstract void Dispose();
}

public class Consumer : BaseConsumer
{
    public IFooRepository fooRepository { get; private set; }
    public IBarRepository barRepository { get; private set; }

    public Consumer(IFooRepository fooRepository, IBarRepository barRepository)
    {
        this.fooRepository = fooRepository;
        this.barRepository = barRepository;
    }

    public override void Dispose()
    {
        this.fooRepository.Dispose();
        this.barRepository.Dispose();
    }
}

[TestClass]
public class ConfigurationTest
{
    private IWindsorContainer container;

    [TestMethod]
    public void SameDatabaseIsUsed()
    {
        IFooRepository fooRepository;
        IBarRepository barRepository;
        using (Consumer consumer = container.Resolve<Consumer>())
        {
            fooRepository = consumer.fooRepository;
            barRepository = consumer.barRepository;
            Assert.AreEqual(fooRepository.Database, barRepository.Database); //FAILS
        }
        Assert.IsNull(fooRepository.Database);
        Assert.IsNull(barRepository.Database);

    }

    [TestMethod]
    public void DifferentDatabaseIsUsed()
    {
        IFooRepository fooRepository;
        IBarRepository barRepository;

        using (Consumer consumer = container.Resolve<Consumer>())
            fooRepository = consumer.fooRepository;

        Assert.IsNull(fooRepository.Database);

        using (Consumer consumer2 = container.Resolve<Consumer>())
            barRepository = consumer2.barRepository;

        Assert.IsNull(barRepository.Database);
    }

    [TestInitialize]
    public void SetUp()
    {
        container = new WindsorContainer().Register(
            Component.For<Database>().ImplementedBy<Database>().LifeStyle.Singleton,
            AllTypes.FromThisAssembly()
                    .BasedOn<IRepository>().WithService.FromInterface()
                    .Configure(c => c.LifeStyle.Transient),
            Component.For<Consumer>().ImplementedBy<Consumer>().LifeStyle.Transient
                    );
    }
}
slamidtfyn
+2  A: 

You always get a new instance when requesting a transient component, if it's not what you want don't use the transient lifestyle :-)

Why would you register a transient component, but attempt to resolve the same object depending on some kind of "context"? Most likely the lifestyle is wrong for the situation, and you will be in trouble trying to coerce it into something it's not.

What you want is something like a contextual lifestyle, mentioned in this article. The below two gists have an implementation for this:

This will allow you do this:

Register(Component.For<Database>().LifeStyle.Scoped())

[TestMethod]
public void SameDatabaseIsUsed()
{
    using (container.BeginScope())
    {
        Consumer consumer = container.Resolve<Consumer>();
        IFooRepository fooRepository = consumer.fooRepository;
        IBarRepository barRepository = consumer.barRepository;
        Assert.AreEqual(fooRepository.Database, barRepository.Database); // YAY!
    }
}

Hope this helps!

roelofb
when i say context, I mean that if i get an instance of an object from my container that depends on two different repositories and those repositories both depend on a fourth object that deals with database operations. Then i would like a behavior were the same database object is injected into the repositories. But if another object is created later with one or more repositories, then the database object is a new instance instead of the same as the one that were injected into my first object. The lifestyle that could do something like this is "PerWebRequest", but I'm in a WinForm world :-(.
slamidtfyn
Yes, it works. Thanks.
slamidtfyn