Платформа Объекта.NET и транзакции

Будучи плохо знакомым с Платформой Объекта, я действительно скорее застреваю о том, как возобновить этот набор проблем. На проекте я в настоящее время продолжаю работать, весь сайт в большой степени интегрируется с моделью EF. Сначала, доступом к контексту EF управляли с помощью Внедрения зависимости bootstrapper. По операционным причинам мы не смогли пользоваться библиотекой DI. Я удалил это и использовал модель отдельных экземпляров объекта контекста при необходимости. Я начал получать следующее исключение:

Тип 'XXX' был отображен несколько раз.

Мы пришли к выводу, что различные экземпляры контекста вызывали эту проблему. Я затем абстрагировал объект контекста в единственный статический экземпляр, к которому получал доступ каждый поток/страница. Я теперь получаю одно из нескольких исключений о транзакциях:

Новая транзакция не позволяется, потому что существуют другие потоки, работающие на сессии.

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

ExecuteReader требует, чтобы команда имела транзакцию, когда соединение, присвоенное команде, находится в незаконченной локальной транзакции. Свойство Transaction команды не было инициализировано.

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

Эти исключения неустойчивы в лучшем случае но мне удалось заставить сайт входить в состояние, где новым соединениям отказали из-за блокировки транзакции. К сожалению, я не могу найти детали исключения.

Я предполагаю, что мой первый вопрос, модель EF должна использоваться от статического единственного экземпляра? Кроме того, действительно ли возможно устранить необходимость транзакций в EF? Я попытался использовать a TransactionScope объект без успеха...

Чтобы быть честным, я много застреваю здесь и не могу понять, почему (что должно быть) довольно простые операции вызывают такую проблему...

32
задан Steven 22 November 2014 в 10:38
поделиться

2 ответа

Создание одной глобальной Entity Framework DbContext в веб-приложении - это очень плохо. Класс DbContext не является потокобезопасным (и то же самое относится к классу ObjectContext Entity Framework v1). Он построен на концепции единицы работы , и это означает, что вы используете его для работы в одном сценарии использования: например, для бизнес-транзакции. Он предназначен для обработки одного единственного запроса.

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

Пожалуйста, подумайте, почему это не сработает. DbContext содержит локальный кеш сущностей в вашей базе данных. Это позволяет вам внести кучу изменений и, наконец, отправить эти изменения в базу данных. При использовании одного статического DbContext , когда несколько пользователей вызывают SaveChanges для этого объекта, как он должен знать, что именно следует зафиксировать, а что нет?

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

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

Также обратите внимание, что наличие одного DbContext на поток так же плохо, как наличие одного единственного экземпляра для всего веб-приложения. ASP.NET использует пул потоков, что означает, что ограниченное количество потоков будет создано в течение жизненного цикла веб-приложения. По сути, это означает, что экземпляры DbContext в этом случае будут жить в течение всего времени существования приложения, вызывая те же проблемы с устареванием данных.

Вы можете подумать, что наличие одного DbContext на поток на самом деле является потокобезопасным, но обычно это не так, поскольку ASP.NET имеет асинхронную модель, которая позволяет завершать запросы в другом потоке, чем где он был запущен (а последние версии MVC и Web API даже позволяют произвольному количеству потоков обрабатывать один запрос в последовательном порядке). Это означает, что поток, запустивший запрос и создавший ObjectContext , может стать доступным для обработки другого запроса задолго до завершения этого первоначального запроса. Однако объекты, используемые в этом запросе (например, веб-страница, контроллер или любой бизнес-класс), могут по-прежнему ссылаться на этот DbContext .Поскольку новый веб-запрос выполняется в том же потоке, он получит тот же экземпляр DbContext , что и старый запрос. Это снова вызывает состояние гонки в вашем приложении и вызывает те же проблемы безопасности потоков, что и один глобальный экземпляр DbContext .

62
ответ дан 27 November 2019 в 20:25
поделиться

Поскольку в своем вопросе вы ссылаетесь на "сайт", я предполагаю, что речь идет о веб-приложении. Статические члены существуют только один раз для всего приложения, если вы используете шаблон типа singleton с одним экземпляром контекста для всего приложения, всевозможные запросы будут находиться во всевозможных состояниях во всем приложении.

Одиночный статический контекст не будет работать, но несколько экземпляров контекста на поток также будет проблематично, так как вы не сможете смешивать и сопоставлять контексты. Что вам нужно, так это один контекст на поток. Мы сделали это в нашем приложении, используя шаблон инъекции зависимостей. Наши классы BLL и DAL принимают контекст в качестве параметра в методах, таким образом, вы можете сделать что-то вроде следующего:

using (TransactionScope ts = new TransactionScope())
{
    using (ObjectContext oContext = new ObjectContext("MyConnection"))
    {
        oBLLClass.Update(oEntity, oContext);
    }
}

Если вам нужно вызвать другие методы BLL/DAL в рамках вашего обновления (или любого другого метода, который вы выбрали), вы просто передаете тот же контекст. Таким образом, обновления/вставки/удаления будут атомарными, все в рамках одного метода будет использовать один и тот же экземпляр контекста, но этот экземпляр не будет использоваться другими потоками.

6
ответ дан 27 November 2019 в 20:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: