1

I'm using ASP.NET Core, and its builtin DI container. I'm using a third-party library (NLog) which I can't change.

My Foo class has a dependency (by constructor injection).

public class Foo {
  private readonly IMyContext _context;
  public Foo(IMyContext context) { _context = context; }
  // etc.
}

However the library caches the Foo instance for the duration of the app (that's outside my control). That means it also caches the dependency. And that dependency must not be cached, because it's an EF context which should be scoped.

An alternative is to inject IServiceProvider, and then to create instances myself.

public class Foo {

  private readonly IServiceProvider _sp;
  public Foo(IServiceProvider sp) { _sp = sp; }

  // etc.

  public void bar() {
    var context = _sp.GetService<IMyContext>();
    // use it
  }

}

But as before, that IServiceProvider instance would be cached for the lifetime of the app.

Is that "safe"? Are there negative repercussions I should know about?

Julian
  • 33,915
  • 22
  • 119
  • 174
grokky
  • 8,537
  • 20
  • 62
  • 96

2 Answers2

4

You don't want to inject your IoC container anywhere. That's a bad practice that allows for sloppy coding, and makes unit testing harder, amongst many other reasons.

Instead introduce a factory that can be injected and create a context on demand:

public interface IDbContextFactory<TContext>
{
    TContext Create();
}

public class DbContextFactory<TContext> : IDbContextFactory<TContext>
    where TContext : DbContext
{
    private readonly Func<TContext> _contextCreator;

    public DbContextFactory(Func<TContext> contextCreator)
    {
        _contextCreator = contextCreator;
    }

    public TContext Create()
    {
        return _contextCreator();
    }
}

Now if you inject this into your Foo:

public class Foo 
{
    private readonly IDbContextFactory<MyContext> _contextFactory;
    public Foo(IDbContextFactory<MyContext> contextFactory)
    { 
        _contextFactory = contextFactory;
    }

    public void bar() {
    {
        using (var context = _contextFactory.Create())
        {
            // use your freshly instantiated context
        }
    }
}

Any decent dependency injection framework can resolve the Func<TContext> parameter of the DbContextFactory, and pass a func there that creates an instance per request.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • This looks great. I've implemented it but couldn't get it to work. Turns out the builtin DI container [doesn't support delegates](http://stackoverflow.com/questions/35736070/how-to-use-funct-in-built-in-dependency-injection) this way! How unfortunate, this answer would have been awesome. So this is excellent in the generic case, but doesn't work for me. I'm conflicted whether to mark it as the answer, because maybe someone else has some advice for my environment – grokky Mar 10 '17 at 11:13
  • @grokky no problem. It works with Autofac, for example, don't know if that is an option. You don't have to mark this as answer, perhaps someone else has anothe idea. – CodeCaster Mar 10 '17 at 11:55
  • Injecting a factory is the solution but you don't always know if the dependency is `IDisposable` that's why *Autofac* introduce `Owned` (which, as far as I know, doesn't exist in the .net framework :(). In such cases my services often relies on `Func>` – Cyril Durand Mar 10 '17 at 14:18
  • @CodeCaster You've convinced me to move to Autofac. I have it working for everything else. But for this, I can't figure out the registration. I tried `services.AddScoped();` then `services.AddScoped();` then `services.AddScoped, DbContextFactory>();` To test this, in the `using` block I logged `context.GetHashCode()` which should be different each time, but they are always equal. – grokky Mar 10 '17 at 17:40
  • @grokky register it with InstancePerDependency(). – CodeCaster Mar 10 '17 at 17:49
0

Addendum to @CodeCaster's excellent solution.

It's important to change Func<Dependency> to Func<Owned<Dependency>>. Otherwise the container won't return new instances each time, even though you're using a delegate factory.

Probably has something to do with the fact that a long-lived component (singleton) depends on a short-lived component (per-transaction). The Func<> prevents the captive dependency bug, but for the factory to also give you new instances each time, it needs to be Owned<>.

I don't understand why, and it seems counter-intuitive, but that's how it works.

grokky
  • 8,537
  • 20
  • 62
  • 96