Как pNet Core Autofa c, удаляющий мой DbContext, даже если он зарегистрирован как SingleInstance - PullRequest
1 голос
/ 09 марта 2020

В нашем приложении есть API, который будет хранить некоторые данные в базе данных. Мы используем Entity Framework Core 3.1.1. После сохранения этой сущности сообщение отправляется на Azure Servicebus, и потребитель будет читать это сообщение, сохраняя сообщение в другой таблице в том же DbContext.

Насколько я понимаю, LifetimeScope определяется для каждого запроса к API. LifetimeScope начнется, когда запрос API достигнет ApiController, и завершится, когда конечная точка обработает запрос.

Проблема, с которой мы сталкиваемся, связана с удалением DbContext, поэтому она не может Быть использованным в Потребителе согрешило его утилизировать.

Из нашего кода довольно очевидно, почему он не работает. DbContext регистрируется следующим образом:

containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));

Мы устанавливаем InstancePerLifetimeScope при регистрации DbContext, в результате чего DbContext удаляется после завершения запроса API и выдает ObjectDisposedException когда мы пытаемся использовать его в потребителе.

Так же, как эксперимент, я попытался зарегистрировать DbContext как SingleInstance следующим образом:

containerBuilder.RegisterType<TDbContext>().AsSelf().SingleInstance().As<IDbContext>().IfNotRegistered(typeof(TDbContext));

Но он по-прежнему выдает исключение ObjectDisposedException, когда мы пытаемся использовать это в Потребителе.

Есть идеи, почему это происходит и как мы можем это решить?

РЕДАКТИРОВАТЬ:

DbContext регистрируется с помощью метода расширения и передается в как дженерик c:

public static class ContainerBuilderExtensions 
{
    public static void RegisterDbContext<TDbContext>(this ContainerBuilder builder) 
    {
        containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
    }
}

1 Ответ

3 голосов
/ 09 марта 2020

Предполагая, что вы звоните по этому

containerBuilder.RegisterType<TDbContext>().AsSelf().SingleInstance().As<IDbContext>().IfNotRegistered(typeof(TDbContext));

после этого

containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerLifetimeScope().As<IDbContext>().IfNotRegistered(typeof(TDbContext));

вторая регистрация не будет иметь никакого эффекта.

Во-вторых, зарегистрировать DbContext как синглтон - не очень хорошая идея. Это не потокобезопасно.

Вместо этого вы можете внедрить IServiceProvider в классе, где зарегистрирован потребитель для служебной шины, и разрешить контекст вручную при использовании нового сообщения.

Вариант 1: использование IServiceProvider

using (var scope = _serviceProvider.CreateScope())
{
   using (var context = scope.ServiceProvider.GetRequiredService<IDbContext>())
   {
      // do work here
   }
}

Вариант 2: использование IServiceProvider и приведение к ILifetimeScope с использованием расширения, доступного с Autofac.Extensions.Microsoft.DependencyInjection версии 5.

using (var lifetimeScope = _serviceProvider.GetAutofacRoot().BeginLifetimeScope())
{
   using (var context = lifetimeScope.Resolve<IDbContext>())
   {
      // do work here
   }
}

Это создаст экземпляр IDbContext каждый раз, когда вам это нужно, и удалит его. Хотя это может занять немного больше времени, это не должно быть проблемой, если сообщения используются в фоновом режиме.

EDIT : Вы также можете зарегистрировать DbContext как Transient

containerBuilder.RegisterType<TDbContext>().AsSelf().InstancePerDependency().As<IDbContext>().IfNotRegistered(typeof(TDbContext));
...