使用 Autofac 在依赖链中选择 LifetimeScope 的更简洁方法

Cleaner way to choose LifetimeScope in a dependency chain with Autofac

我正在使用带有 Autofac 的网络应用程序将服务注入控制器。这些服务有时会与其他服务和存储库一起注入。存储库注入 DbContexts。这 3 层(服务、存储库、上下文)都已注册到 Autofac。我的默认生命周期是 InstancePerLifetimeScope。

不幸的是,我在特定控制器中有一些代码想要在并行线程中执行。由于 DbContext 不是线程安全的,这意味着我需要为每个线程提供一个工厂方法来解析每个依赖项生命周期范围内的服务,而这又需要解析每个依赖项存储库和数据库上下文。

我正在考虑的选项是为每个线程创建一个新的生命周期范围,或者使用命名或键控注册来使用单独的注册来解析每个依赖项服务。

为每个线程创建一个新的生命周期范围的挑战是我需要访问一些每个范围的对象。有些对象需要被继承并且不在新范围内创建新实例,但其他对象(非线程安全的 DbContexts)需要在新范围内生成新实例。在创建新的生命周期范围时,我不知道如何隐式控制这种行为。

另一种方法是使用注册码,这样当我在每个线程上执行工厂方法来解析服务时,它会在每个依赖范围内解析一个服务。如果服务没有依赖关系,这将起作用,但由于它依赖于一堆存储库或服务,默认生命周期范围设置为 InstancePerLifetimeScope,我必须写这样的东西:

    builder.RegisterType<MyService>()
        .As<IMyService>()
        .Named<IMyService>(RegistrationKeys.PerDependency)
            .WithParameter(new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(IMyRepository),
            (pi, ctx) => ctx.ResolveNamed<IMyRepository>(RegistrationKeys.PerDependency))
        ).InstancePerDependency();

由于存储库依赖于 DbContext,因此必须使用此注册名称分别注册每个存储库。并且需要将其配置为使用注册名称解析 DbContext。并且需要使用注册名注册DbContext。

有 10 个服务,每个服务使用大约 4-5 个存储库,我打赌我必须编写的样板注册代码的数量将在 10-20 整页左右。它不会是可维护的。

所以我的问题是,有没有一种方法可以创建特定类型的生命周期范围,让我可以轻松控制哪些对象将拥有新实例,或者哪些对象将从不会继承的父生命周期范围继承打破 asp.net 每个请求的生命周期范围?

或者有没有一种方法可以注册或解析服务以显式解析其所有依赖项在同一范围内而不依赖于它们的默认注册并且无需硬编码全部第二组注册?

The challenge with creating a new lifetime scope per thread is that I need access to some per-scope objects. Some objects would need to be inherited and to not have a new instance created in the new scope, but other objects (the non-thread-safe DbContexts) need to have new instances generated in the new scope. I have no idea how to control this behavior implicitly when creating my new lifetime scope.

这是 InstancePerRequest 解决的挑战。您可以创建子作用域,范围为 Request 的对象将在子作用域之间共享。为此,使用标记的 lifetimescope 和 InstancePerMatchingLifetimeScope

可以在官方文档中看到InstancePerRequest and Tagging a lifetime scope

示例:

builder.RegisterType<Service>().As<IService>().InstancePerMatchingLifetimeScope("KEY");
builder.RegisterType<DbContext>().InstancePerLifetimeScope();

// ...

using (ILifetimeScope scope = container.BeginLifetimeScope("KEY"))
{
    scope.Resolve<IService>(); // Instance #1

    using (ILifetimeScope childScope = scope.BeginLifetimeScope())
    {
        childScope.Resolve<DbContext>();
        childScope.Resolve<IService>(); // shared instance (#1)
    }
}

但这意味着您必须将所有 InstancePerLifetimeScope 更改为 InstancePerMatchingLifetimeScope 并且可以控制 工作单元 生命周期范围的创建非常困难。

另一种方法是使用 Owned<T>Func<T>。您可以在此处获取更多信息:Owned instance

builder.RegisterType<Service>().As<IService>().InstancePerLifetimeScope();
builder.RegisterType<DbContext>().InstancePerLifetimeScope();
builder.RegisterType<Operation>().As<IOperation>().InstancePerLifetimeScope();


public class Operation : IOperation 
{
    public Operation(Func<Owned<DbContext>> contextFactory, IService service)
    {
        this._contextFactory = contextFactory;
        this._service = service; 
    }
    private readonly Func<Owned<DbContext>> _contextFactory;
    private readonly IService _service;

    public void Do() 
    {
        using Owned<DbContext> context = this._contextFactory(); 

        context.Value // => new instance 
        this._service // shared instance (#1) 

    }
}

using (ILifetimeScope scope = container.BeginLifetimeScope())
{
    scope.Resolve<IService>(); // Instance #1

    IEnumerable<IOperation> operations = scope.Resolve<IEnumerable<IOperation>>();
    operations.AsParallel()
              .ForAll(operation => operation.Do());
}

此解决方案的唯一缺点是您的服务将依赖于 Autofac,但如果您不想要它,可以很容易地在 Owned

如果您不想使用 Owned<T> 或您自己的抽象而不是尝试使 DbContext 成为一个特例,您可以扭转问题并在您的自定义范围之间手动共享一些依赖性。

类似于:

using ILifetimeScope childScope = scope.BeginLifetimeScope(b => {
    b.Register<XContext>(c => scope.Resolve<XContext>()).ExternallyOwned(); 
});

var operation = childScope.Resolve<IOperation>();
operation.Do();

这样 IOperation 将在新范围内解析,但 XContext 将来自父范围