. net временный контекст базы данных удален преждевременно - PullRequest
0 голосов
/ 19 февраля 2020

Я перемещаю приложение asp. net mvc5, используя EF6, в asp. net core MVC 3.0, используя EF Core.

В моем приложении mvc5 у меня есть некоторые административные операции которые изменяют базу данных и занимают много времени, поэтому я использую шаблон, когда создаю новый DBContext, который не связан с контекстом запроса, а затем запускаю задачу в фоновом режиме с помощью Task.Run. Это работало отлично в течение многих лет.

При преобразовании в ядро. net было неясно, как создать новый DBContext так, как я делал это в своей старой кодовой базе. Кажется, что я должен быть в состоянии создать Transient DBContext в этих случаях, и все должно быть в порядке.

Поэтому я создал подкласс MyDbContext с именем MyTransientDbContex и в своем классе Configure добавил эту службу:

            services.AddDbContext<MyTransientDbContex>(options =>
                options.UseSqlServer(
                    context.Configuration.GetConnectionString("MyContextConnection")),
                ServiceLifetime.Transient, ServiceLifetime.Transient);

В моем контроллере я вставляю контекст в действие, которому требуется временный сервис, и порождаю поток, чтобы что-то с ним сделать:

public ActionResult Update([FromServices] MyTransientContext context) {

    Task.Run(() => 
    {
        try {
            // Do some long running operation with context
        }
        Catch (Exception e) {
            // Report Exception
        }
        finally {
            context.Dispose();
        }
    }

    return RedirectToAction("Status");
}

Я не ожидал, что мой переходный контекст будет удален до наконец блок. Но я получаю это исключение при попытке доступа к контексту в фоновом потоке:

Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyTransientContext'.'

И действительно, для объекта контекста установлен флаг _disposed true.

Я поставил точку останова на конструкторе для MyTransientContext и «Сделал идентификатор объекта» этого указателя, чтобы я мог отслеживать объект. Этот временный объект создается и является тем же, который вводится в действие моего контроллера. Это также тот же объект, на который я пытаюсь ссылаться, когда генерируется исключение.

Я попытался установить точку останова данных на элементе _disposed, чтобы вызвать стек вызовов, когда для disposed установлено значение true, но точка останова не будет привязана.

Я также попытался переопределить метод Dispose для MyTransientContext, и он не вызывался до тех пор, пока мое явное удаление не будет выполнено в блоке finally, который происходит после того, как исключение было сгенерировано и перехвачено.

Я чувствую, что мне здесь не хватает чего-то фундаментального. Разве не для этого нужны временные сервисы? Что бы избавиться от службы Transient?

Одна последняя деталь - MyTransientContext является производным от MyContext, который, в свою очередь, является производным от IdentityDbContext (Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContex)

Редактировать: Причина, по которой я пошел по пути использования Transient, была из-за этой базовой страницы документа: https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext. В нем говорится, что «... любой код, который явно выполняет несколько потоков параллельно, должен гарантировать, что экземпляры DbContext никогда не будут доступны одновременно. Используя внедрение зависимостей, это может быть достигнуто либо путем регистрации контекста в качестве области действия, либо создания областей (с использованием IServiceScopeFactory) для каждого потока или путем регистрации DbContext в качестве переходного (используя перегрузку AddDbContext, которая принимает параметр ServiceLifetime). "

Как указывало xabikos, это может быть переопределено областью действия asp. net Система DI, где все выглядит так, как будто все, что создано этой системой, ограничено контекстом запроса, включая временные объекты. Может кто-нибудь указать, где это задокументировано, чтобы я мог лучше понять, как работать с ограничениями?

Ответы [ 2 ]

1 голос
/ 19 февраля 2020

Если вы хотите управлять сроком службы, вы можете создать его вручную (или использовать фабрику):

public ActionResult Update()
{
    Task.Run(() => 
    {
        using(var context = new MyTransientContext(...))
        {
            try 
            {
                // Do some long running operation with context
            }
            catch (Exception e) 
            {
                // Report Exception
            }
        }
    }
    return RedirectToAction("Status");
}

Или вы можете использовать IServiceProvider для получения и управления службой:

public class MyController
{
    private IServiceProvider _services;

    public MyController(IServiceProvider services)
    {
        _services = services;
    }

    public ActionResult Update()
    {
        var context = (MyTransientContext)_services.GetService(typeof(MyTransientContext));
        Task.Run(() =>
        {
            using (context)
            {
                try
                {
                    // Do some long running operation with context
                }
                catch (Exception e)
                {
                    // Report Exception
                }
            }
        }
        return RedirectToAction("Status");
    }
}
0 голосов
/ 19 февраля 2020

Вы смешали понятия временных объектов, которые создаются внутренним контейнером DI asp. * Ядро net.

Вы конфигурируете MyTransientContext как переходные во внутренней системе DI. Это практически означает, что каждый раз, когда создается область действия, возвращается новый экземпляр. Для приложения asp. net эта область соответствует HTTP-запросу. Когда запросы заканчиваются, тогда все объекты удаляются, если это применимо.

Теперь в вашем коде, это метод синхронного действия, при котором вы запускаете задачу с Task.Run. Это асинхронная c операция, и вы не await для этого. Практически во время выполнения это будет запущено, но не дождется окончания sh, произойдет перенаправление и запрос будет завершен. В этот момент, если вы попытаетесь использовать внедренный экземпляр, вы получите исключение.

Если вы хотите решить эту проблему, вам нужно перейти к асинхронному действию c и ждать на Task.Run. И, скорее всего, вам не нужно создавать новый Task. Но вы должны понимать, что это, вероятно, не лучший способ, так как потребуется, чтобы длительная операция завершилась sh до того, как произойдет перенаправление.

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

...