Почему возникает исключение ObjectDisposedException при работе с DbContext, и в какой момент он освобождается?

Я получаю ошибку: <br/> System.ObjectDisposedException: Невозможно получить доступ к экземпляру контекста, который был уничтожен. Одна из частых причин этой ошибки — попытка использовать экземпляр контекста, полученного через внедрение зависимостей, после его уничтожения. Это может произойти, если вы вызываете 'Dispose' на экземпляре контекста или используете блок using. Если вы используете внедрение зависимостей, рекомендуется позволить контейнеру внедрения зависимостей управлять уничтожением экземпляров контекста. <br/> <br/> Я должен получать System.ObjectDisposedException при выполнении операций над очищенным объектом, но я не очищал db контексты. <br/> Кроме того, у меня возникли вопросы, так как я просто использую builder.Services.AddDbContext в Program.cs. <br/> <br/> Метод, в котором возникает ошибка: <br/> <pre><code class="csharp">public async Task HasBeenSuccesfullyWorked(long id)
    {
        await StatusUpdate(id, TaskStatus.SuccessCompleted, true, TaskStatus.Working);
        //TaskService.cs:line 94
    }
    private async Task StatusUpdate(long id, TaskStatus newStatus, bool wasCompleted = false,
        TaskStatus? filter = null)
    {
        Expression&lt;Func&lt;TaskEntity, bool&gt;&gt; filterFunc = filter == null
            ? x =&gt; x.Id == id
            : x =&gt; x.Id == id &amp;&amp; x.Status.ToString() == filter.ToString();

        //Ошибка возникает здесь
        var foundTask = await dbContext.Tasks.FirstOrDefaultAsync(filterFunc);
        //TaskService.cs:line 127
        //Ошибка возникает здесь

        if (foundTask == null)
        {
            logger.LogCritical("Служба задач не нашла задачу-{x}", id);
            throw new TaskStatusWasNotUpdate;
  • 3 апреля 2025 г. 17:25
Ответы на вопрос 2
Исключение `ObjectDisposedException` возникает в контексте работы с `DbContext` в Entity Framework, когда вы пытаетесь использовать экземпляр контекста, который уже был освобожден (уничтожен). Это может происходить по нескольким причинам, особенно при использовании зависимостей через внедрение зависимостей (Dependency Injection, DI).

### Причины возникновения `ObjectDisposedException`

1. **Контекст уже уничтожен**: Если вы используете `DbContext` внутри блока `using`, то контекст будет автоматически уничтожен по выходу из этого блока. Если после этого вы пытаетесь использовать экземпляр контекста, возникнет исключение `ObjectDisposedException`.

2. **Управление временем жизни контейнера DI**: Если контекст (`DbContext`) создан с неправильным временем жизни (например, как Singleton), он может быть уничтожен раньше, чем его используют. `DbContext` должен быть зарегистрирован с временем жизни `Scoped`, чтобы гарантировать, что он живет в пределах одного HTTP-запроса (или одного логического действия в приложении).

3. **Асинхронная работа**: Если метод, в котором вы используете `DbContext`, вызывается асинхронно, и контекст был освобожден до завершения асинхронной операции.

### Время освобождения `DbContext`

`DbContext` освобождается автоматически, когда:
- Выходите из блока `using` (если он использовался таким образом).
- Завершается HTTP-запрос, если контекст был зарегистрирован в DI с временем жизни `Scoped`.

### Рекомендации по устранению проблемы

1. **Проверьте регистрацию `DbContext` в DI**: Убедитесь, что ваш `DbContext` зарегистрирован с временем жизни `Scoped`:
   ```csharp
   builder.Services.AddDbContext<MyDbContext>(options =>
       options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
   ```

2. **Избегайте использования `using` для `DbContext`**: Если вы используете DI, не оборачивайте `DbContext` в блоки `using`. Контейнер DI сам будет управлять временем жизни контекста.

3. **Проверьте асинхронные операции**: Убедитесь, что вы не используете `DbContext` за пределами его жизненного цикла, особенно при асинхронном программировании.

4. **Передача контекста**: Если ваш метод вызывается из другого контекста (например, в фоне или после завершения HTTP-запроса), убедитесь, что передаете `DbContext` в правильный контекст.

### Пример кода с DI

Убедитесь, что ваш код не использует `DbContext` вне своего жизненного цикла:

```csharp
public class TaskService
{
    private readonly MyDbContext dbContext;

    public TaskService(MyDbContext context)
    {
        dbContext = context;
    }

    public async Task HasBeenSuccesfullyWorked(long id)
    {
        await StatusUpdate(id, TaskStatus.SuccessCompleted, true, TaskStatus.Working);
    }

    private async Task StatusUpdate(long id, TaskStatus newStatus, bool wasCompleted = false,
        TaskStatus? filter = null)
    {
        Expression<Func<TaskEntity, bool>> filterFunc = filter == null
            ? x => x.Id == id
            : x => x.Id == id && x.Status.ToString() == filter.ToString();

        // Здесь ваш контекст должен быть доступен и не должен быть освобожден
        var foundTask = await dbContext.Tasks.FirstOrDefaultAsync(filterFunc);

        if (foundTask == null)
        {
            throw new TaskStatusWasNotUpdate(); // Не забывайте обработать исключения адекватно
        }

        // Логика обновления статуса...
    }
}
```

Если ваши методы будут вызываться из других потоков или логики, проверьте, что они не вызываются после окончания жизненного цикла `DbContext`.
Я не вижу откуда у вас вызывается JobWasExecuted, поэтому ответ пока будет чисто теоретически: контекст БД (потомок DbContext), получаемый из контейнера DI в настроке по умолчанию, имеет время жизни Scoped. То есть, при обработке запросов к веб-серверу он создается отдельно для каждого запроса и очищается при завершении обработки этого запроса. 

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

Так что, если вам нужно получить контекст БД с нестандартным временем жизни, придется отказаться от получения его через DI. Штатным способом получения таких контекстов является сервис фабрики на базе DbContextFactory (его тоже можно получить через DI, как - см. документацию).

PS А ещё, к DbContext нельзя обращаться параллельно из нескольких потоков - но при типичной обработки запроса такого и не случается.
Написано только что
Похожие вопросы