Более чистый способ выбора LifetimeScope в цепочке зависимостей с помощью Autofac - PullRequest
0 голосов
/ 06 ноября 2019

Я использую веб-приложение с сервисами 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?

Или есть ли способ зарегистрировать или разрешить службу, чтобы явно разрешить все ее зависимости в том жеscope не полагаясь на их регистрации по умолчанию и без необходимости жестко кодировать весь второй набор регистраций для всего?

1 Ответ

1 голос
/ 06 ноября 2019

Проблема с созданием новой области действия для каждого потока состоит в том, что мне нужен доступ к некоторым объектам для каждой области. Некоторые объекты должны быть унаследованы и не иметь нового экземпляра, созданного в новой области, но другим объектам (не ориентированным на потоки DbContexts) должны быть созданы новые экземпляры в новой области. Я понятия не имею, как неявно контролировать это поведение при создании моей новой жизненной области.

Это задача InstancePerRequest решить. Вы можете создать дочернюю область, и объект, ограниченный Request, будет совместно использоваться дочерней областью. Для этого используется помеченное время жизни и InstancePerMatchingLifetimeScope.

Вы можете увидеть InstancePerRequest и Пометка продолжительности жизни в официальной документации.

Пример:

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>. Вы можете получить больше информации здесь: Собственный экземпляр

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 будет из родительской области

...