Могу ли я продолжить использование DbContext после того, как он вызвал исключение?

Мне известно о двух разных сценариях, при которых исключения могут возникать при работе с Entity Framework DbContext:

В одном экземпляре DbContext, я хочу поймать эти исключения, попытаться восстановить, если это применимо, а затем повторить операции.

В частности, если вызов SaveChanges выдает исключение из-за тупика, я хотел бы повторить вызов SaveChanges. Я уже знаю, как обнаружить эту ситуацию и выполнить повтор.

Я видел этот ответ здесь, что указывает на то, что соединение SQL не должно использоваться после тупика. Это означает, что я должен перезапустить все DbContext и операцию более высокого уровня для восстановления из таких исключений.

Что я не уверен в том, можно ли продолжать использовать DbContext после того, как он создал исключение, такое как это, Он войдет в непригодное состояние? Будет ли он работать, но не работает правильно? Будет ли SaveChanges больше не транслироваться?

Ответ 1

Если вы не предоставляете DbContext уже открытое SQL-соединение, DbContext откроет и закроет соединение для вас, когда вы вызываете SaveChanges. В этом случае нет опасности поддерживать DbContext вокруг, за исключением того, что объекты, которые DbContext может удерживаться, могут находиться в недопустимом состоянии (поскольку это может быть причиной отказа SQL-запроса).

Здесь приведен пример DbContext, который поддерживается открытым подключением и транзакцией SQL:

using (var connection = new SqlConnection("my connection"))
{
    connection.Open();

    using (var transaction = connection.BeginTransaction())
    {
        using (var context = new DbContext(connection))
        {
            // Do useful stuff.

            context.SaveChanges();
        }

        transaction.Commit();
    }
}

Если вы укажете DbContext с SqlConnection, который выполняется в контексте транзакции, этот ответ.

Обратите внимание, что Entity Framework не будет создавать вложенную транзакцию. Он просто проверяет, связано ли соединение с пользовательской транзакцией. Если SaveChanges уже выполняется в транзакции, транзакция не запускается. Однако Entity Framework не может определить, прервала ли база данных транзакцию из-за серьезного сбоя (например, блокировки базы данных). Поэтому, если первый вызов SaveChanges завершился неудачей с чем-то вроде тупика, и вы поймали и вспомнили SaveChanges, Entity Framework все еще думает, что он работает внутри транзакции.

Это означает, что этот второй вызов выполняется без транзакции, а это означает, что когда операция завершится неудачно, уже выполненные операторы НЕ будут откатны, так как транзакция отката не выполняется.

Проблема оборванной операции SaveChanges могла быть предотвращена, если Entity Framework использовала вложенные транзакции, но она все равно не решила бы общую проблему согласованности.

Entity Framework создает для нас соединения и транзакции, когда мы не предоставляем их явно. Нам просто нужно/нужно напрямую указывать соединение и транзакцию, когда вызов SaveChanges является частью более крупной общей транзакции. Поэтому даже если EF создала для нас вложенную транзакцию и совершила это, прежде чем вернуться из SaveChanges, у нас возникнут проблемы, если мы будем называть SaveChanges второй раз, так как эта "вложенная" транзакция фактически не вложена вообще. Когда EF совершает эту "вложенную" транзакцию, она фактически совершает единственную транзакцию, которая означает, что вся операция, в которой мы нуждались, является атомной, разорвана; все изменения, сделанные с помощью SaveChanges, совершаются, а операции, которые могли возникнуть после этого вызова, не выполнялись. Очевидно, это нехорошее место.

Итак, моральная история состоит в том, что либо вы позволяете Entity Framework обрабатывать соединения и транзакции для вас, и вы можете повторно звонить на SaveChanges без риска, или сами обрабатываете транзакции, и вам придется быстро работать, когда база данных выдает исключение; вы не должны называть SaveChanges снова.