每个生命周期范围内的Autofac实例仅适用于子范围,而不是根目录

时间:2018-01-26 19:31:09

标签: c# autofac

我在我的应用程序中使用autofac并努力使这种情况有效。

我将我的类型注册为PerLifetimeScope,但此规则也适用于根容器。

我有一个实例,只应该为子LifetimeScope内完成的请求共享,但是应该总是为直接发送到根容器的请求返回一个新实例。

更好地描述:

我有一个使用autofac来解决依赖关系的移动应用。

我为应用程序构建了一个全局容器。当用户导航到新页面时,我会从该页面的容器中打开一个新范围。

该页面可以使用EntityFrameworkCore访问内部sqlite数据库。用于访问数据库的DbContext实例对于页面范围内的所有依赖项应该是相同的。

我还有一个ConfigurationsService类,我使用它像字典(键/值),但也存储在数据库中。 此类应该在页面的任何范围之外的数据库中执行更新,而不是与页面中的其他更改交互。

更改单个配置时,它会请求DbContext的实例,进行更改并处置实例。

这里的问题是,当ConfigurationService从容器(而不是范围)请求DbContext实例时,DbContext现在将成为共享实例,并且连接将保持活动状态,直到容器被释放。

我可以为此打开一个范围,但它会让每个属于相同情况的类的范围生命周期得到控制。

更新1:

下一个代码非常好地表示了上面描述的依赖层次结构和情况。

// CLASSES
public class Context
{
}

public class Repository
{
    public Repository(Context context)
    {
        Context = context;
    }

    public Context Context { get; }
}

public class ViewModel
{
    public ViewModel(Repository repository1, Repository repository2)
    {
        Repository1 = repository1;
        Repository2 = repository2;
    }

    public Repository Repository1 { get; }
    public Repository Repository2 { get; }
}

// SAMPLE
var repository1 = container.Resolve<Repository>();
var repository2 = container.Resolve<Repository>();
var viewModel = container.Resolve<ViewModel>();
  1. 来自repository1和repository2变量的Context引用应该是不同的实例。
  2. viewModel.Repository1和viewModel.Repository2的Context引用应该是同一个实例。
  3. ViewModel是许多可以具有类似依赖关系层次结构的类之一。

    我能够让它适应我的情况的方法是创建一个服务,只在非root用户的范围内管理实例作为单例。

    // CLASSES
    public class ScopeSingletons
    {
        private readonly List<object> _instances = new List<object>();
    
        public T Get<T>(Func<T> factory)
        {
            var instance = _instances.OfType<T>().SingleOrDefault();
    
            if (instance == null)
                _instances.Add(instance = factory());
    
            return instance;
        }
    }
    
    // SAMPLE
    var builder = new ContainerBuilder();
    
    builder.RegisterType<ScopeSingletons>().AsSelf().InstancePerLifetimeScope();
    builder.RegisterType<ViewModel>().AsSelf();
    builder.RegisterType<Repository>().AsSelf();
    builder.Register<Context>(c =>
    {
        if (Equals((c as IInstanceLookup)?.ActivationScope.Tag, "root"))
            return new Context();
    
        return c.Resolve<ScopeSingletons>().Get(() => new Context());
    });
    
    var container = builder.Build();
    var scope = container.BeginLifetimeScope();
    
    var repository1 = container.Resolve<Repository>();
    var repository2 = container.Resolve<Repository>();
    var viewModel = scope.Resolve<ViewModel>();
    

    在这种情况下,everthing将按我的需要工作。上下文解析对于根范围内的每个请求都是唯一的,并在子范围内共享。

1 个答案:

答案 0 :(得分:1)

这是......有点复杂,有些暗示可能存在一些设计问题。根据我自己的经验,每当我发现我想做X 时,除了这个奇怪的情况,我想要Y ,这意味着我设计了一些错误。

但是,让我们说出你需要的东西。

来自&#34;要求&#34;这听起来像是ConfigurationServices用法......

  • 需要控制自己的DbContext实例
  • 的创建和处置
  • 通常是单例或以其他方式存在于根容器级别,因为它在范围创建机制的上下文之外使用。
  • 如果在&#34;页面内解决了&#34;它没有&#34;分享&#34;页面为DbContext。 (它可以但它不会 。)

Sounds like a prime use case for the Owned<T> relationship.

我将其设置为......

  • ConfigurationServices是一个无国籍的单身人士。
  • ConfigurationServices中需要上下文时,它会处理上下文的解析,执行和清理。

它看起来像这样:

public class ConfigurationServices
{
  Func<Owned<DbContext>> _contextFactory;
  public ConfigurationServices(Func<Owned<DbContext>> contextFactory)
  {
    this._contextFactory = contextFactory;
  }

  public void DoWork()
  {
    using(var context = this._contextFactory().Value)
    {
      context.DoSomethingInDatabase();
    }
  }
}

Func<T>关系可以动态解决Owned<T>的全新实例。 Owned<T>部分表示容器赢得在处置中保留对它的引用,而是让控制处置。

鉴于ConfigurationServices在这种情况下将是无状态单身,你可以像这样注册它。

builder.RegisterType<ConfigurationServices>()
       .SingleInstance();

无论它在何处被解析,它都来自根容器范围(所有单身人士都这样做),这意味着Func<Owned<DbContext>>也将来自那里......但是因为你正在使用Owned<T>您无法避免内存泄漏。容器不会保留参考资料进行处置。