配置 DbContextConfiguring a DbContext

本文介绍了使用特定 EF Core 提供程序和可选行为通过 DbContextOptions 配置 DbContext 的基本模式。

设计时 DbContext 配置Design-time DbContext configuration

EF Core的设计时工具需要能够发现和创建 DbContext 类型的工作实例,以便收集有关该应用程序的实体类型及其如何映射到数据库架构的详细信息。 只要该工具可以轻松地创建 DbContext,就可以自动执行此过程,因为它的配置方式与在运行时的配置方式类似。

虽然向 DbContext 提供必要的配置信息的任何模式在运行时都可以使用,但在设计时需要使用 DbContext 的工具只能处理有限数量的模式。 设计时上下文创建部分更详细地介绍了这些内容。

配置 DbContextOptionsConfiguring DbContextOptions

DbContext 必须具有 DbContextOptions 的实例才能执行任何工作。 DbContextOptions 实例携带如下配置信息:

  • 要使用的数据库提供程序,通常通过调用方法(如 UseSqlServerUseSqlite)进行选择。 这些扩展方法需要相应的提供程序包,如 Microsoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Sqlite。 方法在 Microsoft.EntityFrameworkCore 命名空间中定义。
  • 任何必需的数据库实例的连接字符串或标识符,通常作为参数传递给上面提到的提供者选择方法
  • 任何提供程序级别的可选行为选择器,通常还链接到对提供程序选择方法的调用中
  • 任何常规 EF Core 行为选择器,通常链接之后或之前提供程序选择器方法

下面的示例将 DbContextOptions 配置为使用 SQL Server 提供程序、包含在 connectionString 变量中的连接、提供程序级别的命令超时,以及在默认情况下 DbContext 执行所有查询的EF Core 行为选择器:

  1. optionsBuilder
  2. .UseSqlServer(connectionString, providerOptions=>providerOptions.CommandTimeout(60))
  3. .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

备注

提供程序选择器方法和上面提到的其他行为选择器方法是 DbContextOptions 或特定于提供程序的选项类的扩展方法。 若要访问这些扩展方法,你可能需要在范围内具有命名空间(通常为 Microsoft.EntityFrameworkCore),并在项目中包含附加包依赖项。

可以通过重写 OnConfiguring 方法或通过构造函数参数从外部来向 DbContext 提供 DbContextOptions

如果同时使用这两个,则 OnConfiguring 最后应用,并且可以覆盖提供给构造函数参数的选项。

构造函数参数Constructor argument

构造函数可以简单地接受 DbContextOptions,如下所示:

  1. public class BloggingContext : DbContext
  2. {
  3. public BloggingContext(DbContextOptions<BloggingContext> options)
  4. : base(options)
  5. { }
  6. public DbSet<Blog> Blogs { get; set; }
  7. }

提示

DbContext 的基本构造函数还接受 DbContextOptions的非泛型版本,但对于具有多个上下文类型的应用程序,不建议使用非泛型版本。

应用程序现在可以在实例化上下文时传递 DbContextOptions,如下所示:

  1. var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
  2. optionsBuilder.UseSqlite("Data Source=blog.db");
  3. using (var context = new BloggingContext(optionsBuilder.Options))
  4. {
  5. // do stuff
  6. }

OnConfiguringOnConfiguring

您还可以在上下文本身中初始化 DbContextOptions。 虽然你可以将此方法用于基本配置,但你通常仍需要从外部获取某些配置详细信息,例如数据库连接字符串。 可以使用配置 API 或其他任何方法来完成此操作。

若要在上下文中初始化 DbContextOptions,请重写 OnConfiguring 方法,并对提供的 DbContextOptionsBuilder调用方法:

  1. public class BloggingContext : DbContext
  2. {
  3. public DbSet<Blog> Blogs { get; set; }
  4. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  5. {
  6. optionsBuilder.UseSqlite("Data Source=blog.db");
  7. }
  8. }

应用程序可以简单地实例化此类上下文,而无需将任何内容传递给构造函数:

  1. using (var context = new BloggingContext())
  2. {
  3. // do stuff
  4. }

提示

此方法不会对其进行测试,除非测试以完整数据库为目标。

将 DbContext 与依赖关系注入一起使用Using DbContext with dependency injection

EF Core 支持将 DbContext 与依赖关系注入容器一起使用。 可以通过使用 AddDbContext<TContext> 方法将 DbContext 类型添加到服务容器中。

AddDbContext<TContext> 将使你的 DbContext 类型、TContext和对应的 DbContextOptions<TContext> 可用于从服务容器中注入。

有关依赖关系注入的其他信息,请参阅下文。

DbContext 添加到依赖关系注入:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddDbContext<BloggingContext>(options => options.UseSqlite("Data Source=blog.db"));
  4. }

这需要将构造函数参数添加到接受 DbContextOptions<TContext>的 DbContext 类型。

上下文代码:

  1. public class BloggingContext : DbContext
  2. {
  3. public BloggingContext(DbContextOptions<BloggingContext> options)
  4. :base(options)
  5. { }
  6. public DbSet<Blog> Blogs { get; set; }
  7. }

(在 ASP.NET Core) 的应用程序代码:

  1. public class MyController
  2. {
  3. private readonly BloggingContext _context;
  4. public MyController(BloggingContext context)
  5. {
  6. _context = context;
  7. }
  8. ...
  9. }

应用程序代码(直接使用 ServiceProvider,不太常见):

  1. using (var context = serviceProvider.GetService<BloggingContext>())
  2. {
  3. // do stuff
  4. }
  5. var options = serviceProvider.GetService<DbContextOptions<BloggingContext>>();

避免 DbContext 线程问题Avoiding DbContext threading issues

Entity Framework Core 不支持在同一个 DbContext 实例上运行多个并行操作。 这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。 因此,始终 await 异步调用,或对并行执行的操作使用单独的 DbContext 实例。

当 EF Core 检测到同时尝试使用 DbContext 实例时,你将看到一条 InvalidOperationException,其中包含类似于下面的消息:

在上一个操作完成之前,在此上下文上启动的第二个操作。 这通常是由使用同一个 DbContext 实例的不同线程引起的,但不保证实例成员是线程安全的。

并发访问未被检测时,可能会导致未定义的行为、应用程序崩溃和数据损坏。

存在一些常见错误,可能会无意中导致同一 DbContext 实例的并发访问:

在对同一 DbContext 启动任何其他操作之前,忘记等待异步操作完成Forgetting to await the completion of an asynchronous operation before starting any other operation on the same DbContext

使用异步方法,EF Core 可以启动以非阻止方式访问数据库的操作。 但是,如果调用方不等待其中某个方法完成,并继续对 DbContext执行其他操作,则 DbContext 的状态可能会损坏(并且很可能会损坏)。

始终等待立即 EF Core 异步方法。

通过依赖关系注入在多个线程之间隐式共享 DbContext 实例Implicitly sharing DbContext instances across multiple threads via dependency injection

默认情况下, AddDbContext扩展方法会将 DbContext 类型注册为范围生存期

这对于大多数 ASP.NET Core 应用程序中的并发访问问题是安全的,因为在给定的时间只有一个线程在执行每个客户端请求,因为每个请求都将获取一个单独的依赖项注入范围(因而单独的 DbContext 实例)。 对于 Blazor 服务器托管模型,使用一个逻辑请求来维护 Blazor 用户线路,因此,如果使用默认注入作用域,则每个用户线路只能提供一个作用域内 DbContext 实例。

任何并行执行多个线程的代码都应确保 DbContext 实例不会同时访问。

使用依赖关系注入,这可以通过以下方式实现:将上下文注册为作用域,并为每个线程创建作用域(使用 IServiceScopeFactory),或将 DbContext 注册为暂时性(使用采用 ServiceLifetime 参数的 AddDbContext 的重载)。

阅读更多More reading