Is it “safe” to cache IServiceProvider for an app&

2019-02-20 16:50发布

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?

2条回答
成全新的幸福
2楼-- · 2019-02-20 17:38

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.

查看更多
萌系小妹纸
3楼-- · 2019-02-20 17:39

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.

查看更多
登录 后发表回答