0

In our solution, we use dependency injection to instantiate instances of a DbContext class which many other services depend on:

var container = new UnityContainer();
container.RegisterType<OurAppDbContext>(new PerThreadLifetimeManager());
// ... A bunch of services that take a new OurAppDbContext as an argument

This successfully results in instantiating a dozen or so services with their own OurAppDbContext instances.

The Problem

The OurAppDbContext constructor (auto-generated by EntityFramework, therefore not editable) is not quite sufficient for our purposes. If we had it our way, every instance would be constructed as such:

OurAppDbContext newDbContext = new OurAppDbContext();
newDbContext.Database.Log += log => Debug.WriteLine(log);
return newDbContext;

Right now, the way we can hook this event is in every constructor of every service that gets one of these instances. e.g.:

public class AdminDataRepository : IAdminDataRepository
{
    private readonly OurAppDbContext _dbContext;
    public AdminDataRepository(OurAppDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbContext.Database.Log += log => Debug.WriteLine(log);
        ...

But we would rather not have this code copy-pasted into 10+ different service constructors as it becomes a maintenance headache, we might miss it in a few places, or they might get out of sync, etc.

Is there a way to instruct the dependency injector to do this to each OurAppDbContext it constructs - before it passes it along to whatever service it's constructing that depends on it?

Alain
  • 26,663
  • 20
  • 114
  • 184
  • 1
    Please be warned that the use of the [PerThreadLifetimeManager](http://unitycontainer.org/api/Unity.Lifetime.PerThreadLifetimeManager.html) is typically a bad idea when running in a web application, especially when your code is asynchronous. Please see [this](https://stackoverflow.com/a/3266481/264697). I'm not that familiar with Unity, but using the `PerRequestLifetimeManager` is probably better. – Steven May 07 '21 at 11:33
  • @Steven that's a huge tip, thanks. I'm very new to this project, and there are lots of cobwebs to clear out. I suspect this is responsible for a chunk of them. Please feel free to edit your answer below to PerRequestLifetimeManager, in case some poor future soul copy-pastes blindly. – Alain May 08 '21 at 12:11

2 Answers2

2

You can make the registration using a factory delegate:

container.RegisterFactory<OurAppDbContext>(c =>
    {
        OurAppDbContext newDbContext = new OurAppDbContext();
        newDbContext.Database.Log += log => Debug.WriteLine(log);
        return newDbContext;
    },
    new PerThreadLifetimeManager());
Steven
  • 166,672
  • 24
  • 332
  • 435
1

A potential solution occurred to me, turns out it works!

We can create our own DBContext class derived from the EF auto-generated one:

public class LoggingOurAppDbContext : OurAppDbContext
{
    public LoggingOurAppDbContext() : base()
    {
        this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
    }
}

And instruct Unity (dependency injection) to create instances of these rather than instances of the EF-generated one.

It should "just work" - without even editing the constructor arguments of all the services that depend on a PortfolioAccumulationDbContext instance - because inheritance makes the two types interchangeable.

I'm familiar with the syntax for providing a concrete type to implement an interface wherever it's required as a dependency:

container.RegisterType<ISomeInterface, SomeConcreteType>();

But I wasn't sure it would work in my case since the base OurAppDbContext auto-generated by entity framework does not implement any kind of interface, all services that require one are expecting an instance of the concrete type OurAppDbContext. It turns out the same syntax can be used to provide a more-derived type to satisfy an already-concrete type argument:

container.RegisterType<OurAppDbContext, LoggingOurAppDbContext>(...);

Problem solved.

Alain
  • 26,663
  • 20
  • 114
  • 184
  • 1
    i was about to suggest the same thing. personally, i'd just register a custom Factory for the base class that produces an instance of a the derived class. (but maybe ask the follow-up question as a separate SO-question?) – Franz Gleichmann May 06 '21 at 19:50
  • I'm not familiar with providing factories to unity, but I did test the above attempt to override a concrete type with a more-derived type (`container.RegisterType(new PerThreadLifetimeManager());`) and was surprised to see that it works! Not sure why I thought it wouldn't, I guess I've just never seen it done before. – Alain May 06 '21 at 19:59