Injecting a distinct object, for one class

Apr 1, 2015 at 9:07 PM
I am working in an MVC WebAPI webservices app, using Entity Framework as my ORM.

I've been injecting my DbContext objects into my controllers and other classes, with a webrequest lifecycle.

My problem is I have one class into which I need to inject a distinct DbContext.

That is, during any single web request, I may access half-a-dozen classes that have dependencies on a MyDbContext object, and they should share the same instance of MyDbContext. I have MyDbContext configured to have a webrequest lifecycle, and SimpleInjector is handling that just fine.

But I have one other class that needs to have a distinct instance of MyDbContext, independent of the one that is being shared by the other classes in the request. And I haven't an idea how to configure that.
Coordinator
Apr 1, 2015 at 9:25 PM
There are many ways of doing this. Could you share some more context of your specific context? For instance, can you show the code in question to give us ab image of what you are trying to achieve. And can you explain why you need this?
Apr 1, 2015 at 9:41 PM
DbContexts provide a unit-of-work object. You can make multiple changes to records, in various tables, then call dbContext.SaveChanges(), and whatever changes have been made get written to the database.

This is exactly what we want, when we have a group of cooperating classes working together to make changes that should be done as a single transaction.

My problem is that I have another class that's doing nothing but writing logging information do a separate table, that should not operate within the same transactional boundaries as the others.

I want to be able to insert a record, and to save the change to the database, without interfering with whatever transactions are going on in the other classes. For that, I need a distinct instance of DbContext. But the other classes are cooperating in a single transactional context, and do need to share a DbContext among themselves.
Coordinator
Apr 2, 2015 at 9:47 AM
Edited Apr 2, 2015 at 9:47 AM
Hi @jdege

The correct answer is probably that you should define a separate DbContext for logging but to get things moving you could register a delegate for the Type that requires a new instance of DbContext
container.Register<ILogger>(() => new MyLogger(new DbContext()));
Does that work for you in this instance?
Coordinator
Apr 2, 2015 at 11:12 AM
I agree with @qujck, having a separate DbContext for logging seems quite appropriate to me. And it seems quite valid to leave this special LoggingDbContext out of your DI configuration and just new up and dispose it inside the MyLogger.Log method. There's no need in adding this to your DI configuration if this special context is solely used in your logger; which is a cross-cutting concern anyway. But even if you can't create a separate context for logging, there's no harm in creating a new instance of that DbContext manually in your logger.

But besides this, I like to make a few other suggestions.

One thing that comes to mind is to inject a factory into your logger:
public class MyLogger {
    private readonly Func<DbContext> contextFactory;
    public MyLogger(Func<DbContext> contextFactory) { 
        this.contextFactory = contextFactory;
    }

    public void Log(LogEntry entry) {
        using (var context = this.contextFactory.Invoke()) {
            // do some logging here.
        }
    }
}

// Register as follows
container.RegisterSingle<ILogger>(new MyLogger(() => new DbContext());
Another option is to use a decorator to allow running the class (and its possible dependencies) into its own scope. Here's an example:
public class LifetimeScopeLoggerProxy : ILogger {
    private readonly Container container;
    private readonly Func<ILogger> decorateeFactory;

    public LifetimeScopeLoggingProxy(Container container, Func<ILogger> decorateeFactory) {
        this.container = container;
        this.decorateeFactory = decorateeFactory;
    }

    public void Log(LogEntry entry) {
        using (container.BeginLifetimeScope()) {
            ILogger decoratee = this.decorateeFactory.Invoke();
            decoratee.Log(entry);
        }
    }
}

// Register as follows:
ScopedLifestyle scopedLifestyle = Lifestyle.Hybrid(
    () => container.GetCurrentLifetimeScope() != null,
    new LifetimeScopeLifestyle(),
    new WebApiRequestLifestyle());

container.Register<DbContext>(scopedLifestyle);
container.Register<ILogger, MyLogger>();
container.RegisterDecorator(typeof(ILogger), typeof(LifetimeScopeLoggerProxy), Lifestyle.Singleton);