Enitity Framework и Using

227
17 июня 2022, 15:30

Подскажите, а является ли нормальной практикой плодить контексты using при работе с Enitity Framework или правильнее передавать объекты сущностей в методы в качестве аргументов?

Вот это я подразумеваю под "Плодить контексты":

DoWork()
{
    using (var db = new Entity())
    {
        // какая-то работа с сущностью
        ...
        DoWork2();
        ...
        // какая-то работа с сущностью
    }
}
DoWork2()
{
    using (var db = new Entity())
    {
        // какая-то работа с сущностью
        ...
    }
}
Answer 1

У многих, кто использует EF, возникает вопрос, как часто мы должны создавать DbContext, как определить оптимальное время жизни контекста.
Возможные варианты времени жизни контекста:

  • Функция
  • Форма
  • Поток
  • Приложение

Чтобы сделать правильный выбор, предлагаю рассмотреть несколько аспектов:

  1. Правильное разрушение контекста (вызов метода Dispose() контекста)
    Вызов метода Dispose является важным шагом при работе с классом DbContext. Используя конструкцию using, гарантируетcя своевременный вызов метода Dispose и тем самым освобождение неуправляемых ресурсов.

    using (MyContext ctx = new MyContext()) 
    { 
      … 
    }
    

    Если забыть вызвать Dispose, то это может привести к утечке открытых соединений к БД и к неосвобождению неуправляемых ресурсов.
    do-i-have-to-call-dispose-on-dbcontext

  2. Стоимость операции создания нового класса контекста
    Операция создания нового контекста недорогая, потому что это, в основном, копирование ссылок метаданных из глобального кеша. Поэтому за стоимость этой операции не стоит беспокоиться.

  3. Использование памяти
    Чем дольше вы используете объект DbContext, тем больше памяти он занимает. Потому что он будет содержать все сущности (entity), о которых он знает, которые были запрошены через запросы, были добавлены либо присоединены. Поэтому держать долго контекст не рекомендуется.

  4. Потокобезопасность
    Класс DbContext не является потокобезопасным. Если мы используем контекст из нескольких потоков, то мы сами должны обеспечить сихронизацию доступа к контексту.
  5. Синхронизация данных с базой данных
    После создания контекста, если не предпринимать дополнительные меры, контекст не видит изменённые данные в БД, которые были сделаны извне. Создавая новый контекст, меры по синхронизации данных с БД предпринимать не нужно.

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

Answer 2

Context в EF, это, по сути, готовая реализация сразу нескольких паттернов:

  • Repository
  • Data Mapper
  • Lazy Load
  • ...
  • Identity Map
  • Unit of Work

Последние два существенно ограничивают возможные варианты времени жизни контекста:

Unit of Work

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

Identity Map

An Identity Map keeps a record of all objects that have been read from the database in a single business transaction. Whenever you want an object, you check the Identity Map first to see if you already have it.

Из двух цитат выше напрямую следует что контект должен жить ровно одну бизнес-операцию:

  • Если он будет жить меньше ("плодить контексты" в вопросе) - то часть кода операции не увидит измененения, внесенные до нее.
  • Если он будет жить дольше, на протяжении нескольких операций - то другие операции будут видеть неактуальные данные (из-за Identity Map) или наоборот, будт видеть изменения, которые еще не были сохранены.

Что такое бизнес-операция (бизнес-транзакция) - зависит от конкретной предметной области. Это не транзакция в терминах баз данных, а более широкое понятие - некое целостное действие в терминах предметной области.

Обычно это вызов кода сервиса / фасада в BL - DoWork, где Work - это какая-то цельная операция, действие. Т.е. обычно это вызов функции верхнего уровня в вашем приложении.

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

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

Выбор контейнера в коде, в рамках которого живет контектст - функция, форма, поток, запрос, приложение - тоже вторичен. Он должне быть следствием, а не причиной времени жизни контекста.

  • Если конкретная функция (DoWork) соответствует бизнес-операции - она должна контролировать время жизни контекста. Если нет (DoWork2) - то нет.
  • Если бизнес-операция растягивается на время отображения формы - то время жизни контекста должно совпадать с формой, и контекст вполне может быть полем формы. Если нет - то нет.
  • Если вы запускаете фоновые операция в отдельном потоке - то время жизни скорее всего совпадет с временем жизни потока. Но не потому, что это фоновая операция, а потому что это фоновая операция.
  • Если у вас консольное приложение, которое выполняет ровно одну бизнес-операцию - то вполне разумно использовать один контекст на приложение.
Answer 3

Ну а с другой стороны: Если загрузка каких-то объектов из базы и их изменение с сохранением происходит в разных местах приложения и еще и по инициативе пользователя, т.е. может быть значительно разнесено по времени - то придётся каждый раз при создании контекста снова загружать нужные объекты, например используя метод Find с сохраненным ранее ключом и работать уже с обновленными объектами. Если же контекст держать открытым, то этого не потребуется. Только нужно будет поработать над гарантированным закрытием этого долгоживущего контекста как только он перестанет быть нужным. Наверно тут нужен компромис - может быть кэш с выгрузкой контекста (контекстов) по времени, хотя это не проще, чем многократная загрузка объектов, но производительнее.

READ ALSO
Подгрузка контента через кнопку "Показать еще"

Подгрузка контента через кнопку "Показать еще"

Есть такой вид списка новостей:

192
Возможно ли написать динамически вложенные if блоки?

Возможно ли написать динамически вложенные if блоки?

Есть задача сортировать пользователей по дате последнего входа в систему

208
Как поместить в state компонента данные из reducer

Как поместить в state компонента данные из reducer

Я получаю данные из JSON-файла в файле actionsjs и устанавливаю их в reducer sections:

213