Срок службы службы основного объекта 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 похоже, что он настроен как Scoped, что при каждом запросе создается экземпляр DbContext.

Итак, первая часть вопроса: Это правда, и если да, то насколько это дорого стоит?

И вторая часть: Если я создам сервис, который потребляет DbContext и предназначен для использования контроллерами, и будет иметь API для управления некоторыми объектами в БД, должен ли он быть зарегистрирован как облачный?

Ответ 1

Да, время жизни по умолчанию для DbContext ограничено. Это предусмотрено таким образом.

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

И чем больше треков DbContext, тем ниже будет производительность. Вот почему вы часто видите DbContext, который используется только в блоке using(var context = new AppDbContext()).

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

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

Если вам требуется более мелкозернистый элемент управления, вы должны использовать шаблон "Единица работы" (который DbContext уже используется).

Для вашего второго вопроса:

Если вы создаете услугу, она должна иметь срок службы, равный той, которая имеет область действия или кратчайший (read: Scoped или transient).

Если вам явно требуется более длительный срок службы для службы, вы должны ввести в свою службу метод DbContext factory или factory.

Вы можете выполнить это с помощью

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 member of the MySingletonService, it lifetime
        // is effectively bound to it. 
        context = contextFactory();
    }
}

Ответ 2

Примечание. В EF Core 2 появился новый метод AddDbContextPool который создает пул контекстов, которые можно использовать повторно. Область действия остается прежней, но экземпляр будет "сброшен" и возвращен в пул. Я бы подумал, что издержки "сброса" будут такими же, как и просто создание нового, но я думаю, что это не так.

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

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

https://docs.microsoft.com/en-us/ef/core/what-is-new/

Ответ 3

По общему признанию, это то, что я узнал. И это от ленивой преданности KISS. Я избежал других сложностей и решил свою проблему преждевременного EF Core. DbContext избавился от проблем с пулами, областями и т.д. Я просто собирал POC без учета асинхронных возвращаемых значений, прикованный цепью от контроллера к репозиторию и т.д. на котором все по сути возвращаются "пустота", т.е. буквально единственное, "задание". Это вызвало итерации среди моих членов DbContext в более низких подпрограммах, чтобы необъяснимым образом избавиться от базового DbContext. Все, что мне нужно было сделать, это заставить каждый асинхронный метод возвращать значение Task<whatever return> и все работало. EF Core не любит асинхронные пустые возвращаемые значения.