Время существования, настройка и инициализация DbContext
В этой статье описаны основные шаблоны для инициализации и настройки экземпляра DbContext.
Время существования DbContext
Время существования DbContext
начинается, когда создается экземпляр, и заканчивается, когда экземпляр удаляется. Экземпляр DbContext
предназначен для выполнения одной единицы работы. Это означает, что время существования экземпляра DbContext
обычно невелико.
Совет
Цитируя Мартина Фаулера (по ссылке выше): "С помощью единицы работы отслеживаются все операции во время бизнес-транзакции, которые могут повлиять на базу данных. Когда вы закончите работу, такая единица определяет, что нужно сделать, чтобы изменить базу данных в результате ваших действий".
Обычная единица работы при использовании Entity Framework Core (EF Core) включает следующее:
- Создание экземпляра
DbContext
. - Отслеживание экземпляров сущности по контексту. Сущности становятся отслеживаемыми
- При необходимости отслеживаемые сущности изменяются для реализации бизнес-правила.
- Вызывается SaveChanges или SaveChangesAsync. EF Core обнаруживает внесенные изменения и записывает их в базу данных.
- Экземпляр
DbContext
удаляется.
Внимание
- Очень важно также удалить DbContext после использования. Это гарантирует, что все неуправляемые ресурсы будут освобождены и что регистрация всех событий или других перехватчиков будет отменена. Так можно предотвратить утечки памяти в тех случаях, если на экземпляр сохраняются ссылки.
- DbContext не является потокобезопасным. Не передавайте контексты в другие потоки. Выполняйте оператор await для всех вызовов синхронизации, прежде чем продолжать использование экземпляра контекста.
- Исключение InvalidOperationException, генероируемое кодом EF Core, может перевести контекст в невосстанавливаемое состояние. Такие исключения указывают на ошибку в программе и не допускают восстановление.
DbContext во внедрении зависимостей для ASP.NET Core
Во многих веб-приложениях каждый HTTP-запрос соответствует одной единице работы. По этой причине в веб-приложениях время существования контекста по умолчанию привязывается ко времени существования запроса.
Приложения ASP.NET Core настраиваются с использованием внедрения зависимостей. Вы можете добавить EF Core в такую конфигурацию с помощью AddDbContext в методе ConfigureServices
файла Startup.cs
. Например:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}
В этом примере регистрируется подкласс DbContext
с именем ApplicationDbContext
в качестве службы с заданной областью в поставщике службы приложений ASP.NET Core (т. е. в контейнере внедрения зависимостей). Контекст при этом настраивается для использования поставщика базы данных SQL Server и считывания строки подключения из конфигурации ASP.NET Core. Обычно не имеет значения, где в ConfigureServices
выполняется вызов к AddDbContext
.
Класс ApplicationDbContext
должен предоставить доступ к общедоступному конструктору с помощью параметра DbContextOptions<ApplicationDbContext>
. Именно так передается конфигурация контекста из AddDbContext
в DbContext
. Например:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Затем ApplicationDbContext
можно использовать в контроллерах ASP.NET Core или других службах посредством внедрения конструктора. Например:
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
Результатом будет экземпляр ApplicationDbContext
, созданный для каждого запроса и переданный контроллеру для выполнения единицы работы до удаления при завершении запроса.
Дополнительные сведения о параметрах конфигурации см. далее в этой статье. Кроме того, дополнительные сведения о конфигурации и внедрении зависимостей можно найти в статьях Запуск приложения в ASP.NET Core и Внедрение зависимостей в ASP.NET Core.
Простая инициализация DbContext с помощью оператора new
Экземпляры DbContext
можно создать средствами .NET, например с помощью оператора new
в C#. Настройку можно выполнить путем переопределения метода OnConfiguring
или передачи параметров в конструктор. Например:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Этот шаблон также упрощает передачу сведений о конфигурации, например строки подключения, через конструктор DbContext
. Например:
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
Кроме того, вы можете использовать DbContextOptionsBuilder
для создания объекта DbContextOptions
, который затем передается в конструктор DbContext
. Это позволяет явно создать DbContext
, настроенный для внедрения зависимостей. Например, при использовании ApplicationDbContext
, определенного для указанных выше веб-приложений ASP.NET Core:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Вы можете создать DbContextOptions
, а конструктор может быть вызван явно:
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
.Options;
using var context = new ApplicationDbContext(contextOptions);
Использование фабрики DbContext (например, для Blazor)
Некоторые типы приложений (например, ASP.NET Core Blazor) используют внедрение зависимостей, но не создают область службы, соответствующую нужному времени существования DbContext
. Даже если такое соответствие отсутствует, приложению, возможно, потребуется выполнять несколько единиц работы в рамках такой области, например в одном HTTP-запросе.
В таких случаях можно использовать AddDbContextFactory для регистрации фабрики для создания экземпляров DbContext
. Например:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}
Класс ApplicationDbContext
должен предоставить доступ к общедоступному конструктору с помощью параметра DbContextOptions<ApplicationDbContext>
. Такой же шаблон используется в традиционных приложениях ASP.NET Core (см. раздел выше).
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Затем фабрику DbContextFactory
можно использовать в других службах посредством внедрения конструктора. Например:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
Внедренную фабрику затем можно использовать для создания экземпляров DbContext в коде службы. Например:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Обратите внимание, что созданными таким образом экземплярами DbContext
не управляет поставщик служб приложения. Поэтому приложение должно удалять их.
Дополнительные сведения об использовании EF Core с Blazor см. в статье ASP.NET Core Blazor Server с Entity Framework Core.
DbContextOptions
Начальная точка для всех конфигураций DbContext
— это DbContextOptionsBuilder. Есть три способа получения этого построителя:
- В
AddDbContext
и связанных методах. - В
OnConfiguring
- При явном создании с помощью оператора
new
.
Примеры всех этих вариантов приведены в предыдущих разделах. Такую же конфигурацию можно применить независимо от того, каким способом получен построитель. Кроме того, OnConfiguring
вызывается в любом случае и независимо от способа создания контекста. Это означает, что OnConfiguring
можно использовать для выполнения дополнительной настройки даже при использовании AddDbContext
.
Настройка поставщика базы данных
Каждый экземпляр DbContext
необходимо настроить для использования только одного поставщика баз данных. (Разные экземпляры подтипа DbContext
можно использовать с разными поставщиками баз данных, но у одного экземпляра должен быть только один поставщик.) Поставщик баз данных настраивается через особый вызов Use*
. Например, следующий код позволяет использовать поставщик базы данных SQL Server:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Приведенные здесь методы Use*
являются методами расширения, которые реализуются поставщиком. Это означает, что пакет NuGet поставщика базы данных нужно установить до использования метода расширения.
Совет
Поставщики базы данных EF Core активно используют методы расширения. Если компилятор указывает, что метод не найден, убедитесь, что пакет NuGet поставщика установлен и что ваш код включает using Microsoft.EntityFrameworkCore;
.
В представленной ниже таблице приведены примеры для популярных поставщиков баз данных.
Система базы данных | Пример конфигурации | Пакет NuGet |
---|---|---|
SQL Server или Azure SQL | .UseSqlServer(connectionString) |
Microsoft.EntityFrameworkCore.SqlServer |
Azure Cosmos DB | .UseCosmos(connectionString, databaseName) |
Microsoft.EntityFrameworkCore.Cosmos |
SQLite | .UseSqlite(connectionString) |
Microsoft.EntityFrameworkCore.Sqlite |
Выполняющаяся в памяти база данных EF Core | .UseInMemoryDatabase(databaseName) |
Microsoft.EntityFrameworkCore.InMemory |
PostgreSQL* | .UseNpgsql(connectionString) |
Npgsql.EntityFrameworkCore.PostgreSQL |
MySQL или MariaDB* | .UseMySql(connectionString) |
Pomelo.EntityFrameworkCore.MySql |
Оракул* | .UseOracle(connectionString) |
Oracle.EntityFrameworkCore |
* Корпорация Майкрософт не предоставляет эти поставщики баз данных. Дополнительные сведения можно найти в статье Поставщики баз данных.
Предупреждение
Выполняющаяся в памяти база данных EF Core не предназначена для использования в рабочей среде. Кроме того, это не самый лучший вариант даже для тестирования. Дополнительные сведения см. в статье Тестирование кода, использующего EF Core.
Дополнительные сведения об использовании строк подключения с EF Core см. в статье Строки подключения.
Необязательная настройка, зависящая от поставщика базы данных, выполняется в дополнительном построителе, предоставляемом поставщиком. Например, вы можете использовать EnableRetryOnFailure для настройки повторных попыток для обеспечения устойчивости при подключении к Azure SQL:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
}
Совет
Этот же поставщик базы данных используется для SQL Server и Azure SQL. Но мы рекомендуем обеспечить устойчивость подключения к Azure SQL.
Подробные сведения о настройке для конкретных поставщиков см. в статье Поставщики базы данных.
Другая конфигурация DbContext
Другую конфигурацию DbContext
можно связать до или после (это не играет роли) вызоваUse*
. Например, чтобы включить ведение журнала для конфиденциальных данных:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.EnableSensitiveDataLogging()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
В следующей таблице приведены примеры популярных методов, вызываемых для DbContextOptionsBuilder
.
Метод DbContextOptionsBuilder | Что он делает | Подробнее |
---|---|---|
UseQueryTrackingBehavior | Задает поведение отслеживания по умолчанию для запросов. | Поведение отслеживания запросов |
LogTo | Простой способ получения журналов EF Core | Ведение журналов, регистрация событий и диагностика |
UseLoggerFactory | Регистрирует фабрику Microsoft.Extensions.Logging . |
Ведение журналов, регистрация событий и диагностика |
EnableSensitiveDataLogging | Отвечает за включение данных приложения в исключениях и ведение журналов. | Ведение журналов, регистрация событий и диагностика |
EnableDetailedErrors | Подробные ошибки запросов (в ущерб производительности). | Ведение журналов, регистрация событий и диагностика |
ConfigureWarnings | Позволяет игнорировать или генерировать предупреждения и другие события. | Ведение журналов, регистрация событий и диагностика |
AddInterceptors | Регистрирует перехватчики EF Core. | Ведение журналов, регистрация событий и диагностика |
UseLazyLoadingProxies | Позволяет использовать динамические прокси-серверы для отложенной загрузки. | Отложенная загрузка |
UseChangeTrackingProxies | Позволяет использовать динамические прокси-серверы для отслеживания изменений. | Ожидается в ближайшее время... |
Примечание.
UseLazyLoadingProxies и UseChangeTrackingProxies — это методы расширения из пакета NuGet Microsoft.EntityFrameworkCore.Proxies. Такой тип вызова ".UseSomething()" рекомендуется для настройки и (или) использования расширений EF Core в других пакетах.
DbContextOptions
и DbContextOptions<TContext>
Большинство подклассов DbContext
, которые принимают DbContextOptions
, должны использовать универсальный вариант DbContextOptions<TContext>
. Например:
public sealed class SealedApplicationDbContext : DbContext
{
public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
}
Это гарантирует, что для конкретного подтипа DbContext
будут разрешены правильные параметры из внедрения зависимостей, даже если зарегистрировано несколько подтипов DbContext
.
Совет
Запечатывать DbContext не обязательно, но рекомендуется для классов, от которых не будут осуществляться наследование.
Но если сам подтип DbContext
предполагает наследование, он должен предоставить доступ к защищенному конструктору, принимающему неуниверсальный DbContextOptions
. Например:
public abstract class ApplicationDbContextBase : DbContext
{
protected ApplicationDbContextBase(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
Это позволяет нескольким конкретным подклассам вызвать такой базовый конструктор с помощью разных универсальных экземпляров DbContextOptions<TContext>
. Например:
public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
: base(contextOptions)
{
}
}
public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
: base(contextOptions)
{
}
}
Обратите внимание, что этот шаблон аналогичен наследованию непосредственно от DbContext
. То есть конструктор DbContext
принимает неуниверсальные DbContextOptions
по этой причине.
Подкласс DbContext
, который предусматривает создание экземпляра и от которого будет осуществляться наследование, должен предоставить доступ к обеим формам конструктора. Например:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
protected ApplicationDbContext(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
Настройка DbContext во время разработки
Средства разработки EF Core, такие как средства для миграции EF Core, должны иметь возможность обнаруживать и создавать рабочие экземпляры типа DbContext
для сбора сведений о типах сущностей приложения и их сопоставления со схемой базы данных. Этот процесс можно автоматизировать, так как средство может без труда создать DbContext
с теми же настройками, которые заданы во время выполнения.
Хотя любой шаблон, предоставляющий DbContext
необходимые сведения о конфигурации, может работать во время выполнения, средства, требующие использования DbContext
во время разработки, можно использовать только с ограниченным количеством шаблонов. Дополнительные сведения об этом см. в статье Создание DbContext во время разработки.
Предотвращение проблем с потоками DbContext
Entity Framework Core не поддерживает выполнение нескольких параллельных операций в одном экземпляре DbContext
, включая параллельное выполнение асинхронных запросов и любое явное использование экземпляра из нескольких потоков одновременно. Поэтому обязательно сразу же применяйте await
к асинхронным вызовам или используйте отдельные экземпляры DbContext
, выполняемые параллельно.
Когда EF Core обнаруживает попытку одновременного использования экземпляра DbContext
, отобразится InvalidOperationException
с примерно таким сообщением:
В этом контексте была начата вторая операция до завершения предыдущей операции. Как правило, так происходит, когда несколько потоков используют один и тот же экземпляр DbContext. Но потокобезопасность членов экземпляров не гарантируется.
Если одновременный доступ не обнаруживается, это может привести к неопределенному поведению, аварийному завершению работы приложения и повреждению данных.
Есть распространенные ошибки, из-за которых может случайно возникнуть одновременный доступ к одному и тому же экземпляру DbContext
.
Ошибки, связанные с асинхронными операциями
Асинхронные методы позволяют EF Core инициировать операции, обращающиеся к базе данных, без блокировки. Но если вызывающий объект не ожидает завершения одного из этих методов и переходит к выполнению других операций с DbContext
, структура DbContext
может быть (и, скорее всего, будет) повреждена.
Обязательно сразу же применяйте await к асинхронным методам EF Core.
Неявное совместное использование экземпляров DbContext путем внедрения зависимостей
Метод расширения AddDbContext
по умолчанию регистрирует типы DbContext
с заданной областью времени существования.
Это обеспечивает защиту от проблем с одновременным доступом в большинстве приложений ASP.NET Core, так как есть только один поток, в котором каждый запрос клиента выполняется в определенный момент времени, и каждый такой запрос получает отдельную область внедрения зависимостей (и, следовательно, отдельный экземпляр DbContext
). В модели размещения Blazor Server используется один логический запрос для обслуживания пользовательского канала Blazor. Поэтому при использовании области внедрения по умолчанию для каждого пользовательского канала доступен только один экземпляр DbContext с заданной областью.
Если код явно выполняет несколько потоков в параллельном режиме, нужно исключить возможность одновременного доступа к экземплярам DbContext
.
При использовании внедрения зависимостей это можно реализовать путем регистрации контекста как ограниченного областью, создания области (с помощью IServiceScopeFactory
) для каждого потока или регистрации экземпляра DbContext
как временного (с помощью перегрузки AddDbContext
, при которой принимается параметр ServiceLifetime
).
Дополнительные материалы
- Дополнительные сведения о внедрении зависимостей см. здесь.
- Подробную информацию см. в статье Тестирование кода, использующего EF Core.