Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описаны основные шаблоны для инициализации и настройки экземпляра DbContext.
Предупреждение
В этой статье используется локальная база данных, которая не требует проверки подлинности пользователя. Рабочие приложения должны использовать самый безопасный поток проверки подлинности. Дополнительные сведения о проверке подлинности для развернутых тестовых и рабочих приложений см. в разделе "Безопасные потоки проверки подлинности".
Время существования DbContext
Время существования DbContext начинается, когда создается экземпляр, и заканчивается, когда экземпляр удаляется. Экземпляр DbContext предназначен для выполнения однойединицы работы. Это означает, что время существования экземпляра DbContext обычно невелико.
Совет
Цитируя Мартина Фаулера (по ссылке выше): "Unit of Work" отслеживает все ваши действия во время бизнес-транзакции, которые могут повлиять на базу данных. Когда вы закончите, система выясняет всё, что необходимо сделать для изменения базы данных в результате вашей работы.
Обычная единица работы при использовании 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 в Program.cs. Например:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string"
+ "'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
Предыдущий код регистрирует ApplicationDbContextподкласс DbContextв качестве службы с областью действия в поставщике служб приложений ASP.NET Core. Провайдер услуг также известен как контейнер для внедрения зависимостей. Контекст настроен на использование поставщика базы данных SQL Server и считывает строку подключения из конфигурации ASP.NET Core.
Класс 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" для получения дополнительной информации.
Основная инициализация DbContext с помощью "new"
DbContext экземпляры можно создать с помощью 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
Некоторые типы приложений (например, 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 async Task 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 in-memory не предназначена для использования в рабочей среде. Кроме того, это не самый лучший вариант даже для тестирования. Дополнительные сведения см. в статье Тестирование кода, использующего 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. | Ведение журналов, регистрация событий и диагностика |
| EnableServiceProviderCaching | Управляет кэшированием внутреннего поставщика услуг | Кэширование поставщика услуг |
| UseMemoryCache | Настройка кэша памяти, используемого 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 не гарантируется.
Если одновременный доступ не обнаруживается, это может привести к неопределенному поведению, аварийному завершению работы приложения и повреждению данных.
Есть распространенные ошибки, из-за которых может случайно возникнуть одновременный доступ к одному и тому же экземпляру DbContext.
Ошибки, связанные с асинхронными операциями
Асинхронные методы позволяют EF Core инициировать операции, обращающиеся к базе данных, без блокировки. Но если вызывающий объект не ожидает завершения одного из этих методов и переходит к выполнению других операций с DbContext, состояние DbContext может быть (и, скорее всего, будет) повреждено.
Обязательно сразу же применяйте await к асинхронным методам EF Core.
Неявное совместное использование экземпляров DbContext путем внедрения зависимостей
Метод расширения
Это обеспечивает защиту от проблем с одновременным доступом в большинстве приложений ASP.NET Core, так как есть только один поток, в котором каждый запрос клиента выполняется в определенный момент времени, и каждый такой запрос получает отдельную область внедрения зависимостей (и, следовательно, отдельный экземпляр DbContext). В модели размещения Blazor Server для обслуживания пользовательского контура Blazor используется один логический запрос, поэтому при использовании дефолтной области внедрения для каждого пользовательского контура доступен только один экземпляр DbContext в заданной области видимости.
Если код явно выполняет несколько потоков в параллельном режиме, нужно исключить возможность одновременного доступа к экземплярам DbContext.
При использовании внедрения зависимостей это можно реализовать путем регистрации контекста как ограниченного областью, создания области (с помощью IServiceScopeFactory) для каждого потока или регистрации экземпляра DbContext как временного (с помощью перегрузки AddDbContext, при которой принимается параметр ServiceLifetime).
ConfigureDbContext для составления конфигурации
Примечание.
В этом разделе рассматриваются сведения об использовании EF Core промежуточного уровня в основном для повторно используемых библиотек и компонентов. Большинство приложений должны использовать шаблон, описанный AddDbContextFactory ранее в этой статье.
Начиная с EF Core 9.0, можно использовать ConfigureDbContext для применения дополнительной конфигурации к DbContext, либо до вызова AddDbContext, либо после него. Это особенно полезно для создания конфигурации, не конфликтующей в повторно используемых компонентах или тестах.
Базовое использование «ConfigureDbContext»
ConfigureDbContext позволяет добавлять конфигурацию в повторно используемой библиотеке или компоненте без замены всей конфигурации поставщика:
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging()
.EnableDetailedErrors());
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("BasicExample"));
Конфигурация для конкретного поставщика без строк подключения
Чтобы применить конфигурацию для конкретного поставщика, можно использовать методы конфигурации для конкретного поставщика, не предоставляя строку подключения. Поставщик SQL Server также включает ConfigureSqlEngine в данном случае. Дополнительные сведения см. в статье о поведении пакетной обработки sql Server .
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.UseSqlServer(sqlOptions =>
sqlOptions.EnableRetryOnFailure()));
services.AddDbContext<BlogContext>(options =>
options.UseSqlServer("connectionString"));
Порядок приоритета ConfigureDbContext и AddDbContext
ConfigureDbContext При использовании обоих AddDbContext методов или при выполнении нескольких вызовов этих методов конфигурация применяется в порядке вызова методов, при этом последующие вызовы имеют приоритет для конфликтующих параметров.
Для параметров, не являющихся конфликтующими (например, добавление журналов, перехватчиков или других параметров), все конфигурации создаются вместе:
var services = new ServiceCollection();
services.ConfigureDbContext<BlogContext>(options =>
options.LogTo(Console.WriteLine));
services.AddDbContext<BlogContext>(options =>
options.UseInMemoryDatabase("CompositionExample"));
services.ConfigureDbContext<BlogContext>(options =>
options.EnableSensitiveDataLogging());
Для конфликтующих параметров последняя конфигурация выигрывает. Дополнительные сведения об этом изменении поведения см. в разделе о критических изменениях в EF Core 8.0 .
Примечание.
Настройка другого поставщика не приведет к удалению предыдущей конфигурации поставщика. Это может привести к ошибкам при создании контекста. Чтобы полностью заменить поставщика, необходимо удалить регистрацию контекста и повторно добавить ее или создать новую коллекцию служб.
Читать далее
- Прочтите Dependency Injection, чтобы узнать больше о использовании DI.
- Подробную информацию см. в статье Тестирование.