Срок службы службы ядра Entity Framework по умолчанию
В приложении ASP.NET Core я могу зарегистрировать DbContext через DI, как это
services.AddDbContext<Models.ShellDbContext>(options => options.UseNpgsql(connection));
И интересно знать, какова его продолжительность жизни?
Отсюда https://github.com/aspnet/EntityFramework/blob/f33b76c0a070d08a191d67c09650f52c26e34052/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs#L140 выглядит так, как будто он создан, выглядит так, как будто он настроен на DB.
Итак, первая часть вопроса: правда ли это, и если да, то насколько это дорого?
И вторая часть: если я создаю сервис, который использует DbContext и предназначен для использования контроллерами, и будет иметь API для управления некоторыми объектами в БД, должен ли он быть также зарегистрирован как Scoped?
2 ответа
Да, время жизни по умолчанию для DbContext
находится в области видимости Это предназначено таким образом.
Инстанцирование DbContext
это довольно дешево, и это гарантирует, что вы не используете много ресурсов. Если бы у вас был DbContext
с единственным временем жизни, все записи, которые вы прочитали один раз, будут отслеживаться DbContext
, если вы специально не отключите отслеживание. Это потребует гораздо большего использования памяти и будет продолжать расти.
И чем больше тем DbContext
треки, тем ниже будет производительность. Вот почему вы часто видите DbContext
используется только в using(var context = new AppDbContext())
блок.
Однако в веб-приложениях, используя using
Блок плох, потому что время жизни управляется платформой, и если вы настроите его на ранние вызовы, после этого произойдет сбой с исключением.
Если вы используете временное время жизни на другой стороне, вы потеряете функциональность "транзакции". С областью, DbContext
имеет объем транзакции, равный запросу.
Если вам нужен более детальный контроль, вы должны использовать шаблон единиц работы (который DbContext
уже вроде как использую).
На ваш второй вопрос:
Если вы создаете сервис, у него должен быть срок службы, равный сроку службы или меньше (читай: Scoped или transient).
Если вам явно требуется более длительный срок службы, вы должны ввести DbContext
Фабричный сервис или фабричный метод к вашим услугам.
Вы можете сделать это с помощью чего-то вроде
services.AddTransient<Func<AppDbContext>>( (provider) => new Func<MyDbContext>( () => new AppDbContext()));
services.AddSingleton<IMySingletonService, MySingletonService>();
И ваш сервис может выглядеть так:
public class MySingletonService : IMySingletonService, IDisposable
{
private readonly AppDbContext context;
public MySingletonService(Func<AppDbContext> contextFactory)
{
if(contextFactory == null)
throw new ArgumentNullException(nameof(contextFactory));
// it creates an transient factory, make sure to dispose it in `Dispose()` method.
// Since it's member of the MySingletonService, it's lifetime
// is effectively bound to it.
context = contextFactory();
}
}
Примечание: в EF Core 2 появился новый метод AddDbContextPool
который создает пул контекстов, которые можно использовать повторно. Область действия остается прежней, но экземпляр будет "сброшен" и возвращен в пул. Я бы подумал, что издержки "сброса" будут такими же, как и просто создание нового, но я думаю, что это не так.
Если этот метод используется, в то время, когда контроллер запрашивает экземпляр DbContext, мы сначала проверим, есть ли экземпляр в пуле. Как только обработка запроса завершается, любое состояние экземпляра сбрасывается, а сам экземпляр возвращается в пул.
Концептуально это похоже на то, как работает пул соединений в провайдерах ADO.NET, и имеет преимущество, заключающееся в экономии некоторых затрат на инициализацию экземпляра DbContext.
По общему признанию, это - то, что я изучил. И это от ленивой преданности KISS. Я избежал других сложностей и решил свою проблему преждевременного EF Core. DbContext избавляется от проблем с пулами, областями и т. Д. Я просто собирал POC без учета асинхронных возвращаемых значений, прикованный цепью от контроллера к репозиторию и т. Д. на котором все по сути вернули "пустоту", т. е. буквально в единственном числе, "задачу". Это заставило итерации среди моих членов DbContext в более низких подпрограммах необъяснимым образом избавиться от базового DbContext. Все, что мне нужно было сделать, это заставить каждый асинхронный метод возвращать Task<whatever return>
ценность и все работало. EF Core не любит асинхронные возвращаемые значения.