【问题标题】:Is it safe to reuse Entity Framework Sqlite DBContext?重用实体框架 Sqlite DBContext 是否安全?
【发布时间】:2026-02-10 02:25:01
【问题描述】:

我会这样做:

  public class MyDbContext : DbContext {
    public MyDbContext(bool readOnlyFlag) {
      // Monitor.Enter(theLock); // needed ??
      this.readOnlyFlag = readOnlyFlag; 
      // Database.EnsureCreated(); // needed ??
    }

    public DbSet<MyData> MyData { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
      string connectionString = "Data Source=C:\\mydb.db;";
      if (readOnlyFlag) connectionString += "Mode=ReadOnly;";
      optionsBuilder.UseSqlite(connectionString);
    }

    public override void Dispose() {
      // Database.CloseConnection(); // needed ??
      base.Dispose();
      // Monitor.Exit(theLock); // needed ??
    }

    readonly bool readOnlyFlag;
    // readonly object theLock = new object(); // needed ??
  }

然后:

using (var dbc = new MyDbContext(true)) {
  dbc.MyData.Where( ... code
}

我从多个并发线程调用此类代码以运行不同的查询..(在 .Net Core 3.0 控制台应用程序中)

问题:

  1. 如果我理解正确,数据库文件将在using 块启动时打开,在它结束时关闭。在每个查询上关闭和打开文件似乎效率很低,但我找不到任何关于保留单例MyDbContext(即在Program 类中)并重用它是否可以的参考?

    李>
  2. 如果我可以重复使用MyDbContext,我是否应该在查询周围使用锁定?

  3. 一般来说,我是否需要使用上面提到的Monitor 来确保查询不会同时运行?我看过帖子说 Sqlite 需要这个?

  4. 我需要打电话给Database.CloseConnection() 吗?没有它似乎可以正常工作,但我看过上面所说的帖子?

  5. Sqlite 需要 Database.EnsureCreated() 吗?

谢谢!

【问题讨论】:

  • 您是否注意到使用单个上下文和多个上下文在效率上的差异?

标签: c# entity-framework sqlite .net-core


【解决方案1】:

您确定您是数据的唯一用户吗?换句话说,您确定数据不会在您的 dbContext 的两次使用之间发生变化吗?

此外:您确定您的 dbContext 将始终以这种方式使用,或者将来此 dbContext 可能会连接到真实数据库?

如果您的线程将是唯一的用户,现在和将来,重用 DbContext 并没有太大的危害。但是,请记住,在 Dispose dbContext 之前,并不能保证数据真的被写入。此外:您的 dbContext 会将所有获取的数据保存在本地内存中,因此一段时间后您将在本地内存中拥有完整的数据库。

考虑使用存储库模式,您可以在其中隐藏数据的持久化方式,存储库模式更了解存储库的用途,并且可以更明智地决定将哪些数据保存在内存中以及从哪些数据中查询您的数据库由一个新的 dbContext。

例如,如果您有一个包含 Schools、Students 和 Teachers 的数据库,并且您经常查询他们的数据,但很少查询退休教师的数据和毕业学生的数据,那么您的存储库可以保留所有获取的非退休/毕业教师/学生在内存中,只创建一个新的 dbContext 来获取未知数据、获取退休/毕业数据或更新数据库

interface IRepositorySet<Tentity> : IEnumerable<Tentity>
     where Tentity : class, new()
{
     Tentity Add(Tentity entity);
     Tentity Update(Tentity entity);
     Tentity Delete(Tentity entity);
}
interface ISchoolsRepository
{
     // for simple queries / add / update / remove only
     IRepositorySet<School> Schools {get;}
     IRepositorySet<Teacher> Teachers {get;}
     IRepositorySet<Student> Students {get;}
}

RepositorySet 知道当它需要数据时要创建哪个 dbContext。所有经常获取的项目都将保存在带有主键的字典中。

在创建时,Dictionary 会填充所有主键,并且值为 null,表示尚未获取该项目。

当请求数据时,RepositorySet 首先从字典中获取数据。所有仍为空值的项目将从新的 dbContext 中获取并放入字典中。

请注意,这不适用于大量数据。如果您认为可以将所有获取的数据保存在内存中,请仅考虑此解决方案。但话又说回来:保持 dbContext 打开也会将所有获取的数据保留在内存中。

【讨论】:

  • 谢谢!我当然会改变数据,正如我所说,这是一个多线程应用程序,其中多个线程可以同时运行查询/更新/事务,你是什么意思是“真正的数据库”?这个项目只会使用 sqllite
  • 您写过关于每个连接 dbContext 的打开和关闭文件。所以我认为您使用的数据库提供程序在功能上是有限的。智能数据库管理系统足够智能,可以检测到它的一些数据需要保存在内存中,因为它们经常需要,就像我在使用字典的解决方案中所做的那样
【解决方案2】:

您可以将 DbContext 与 Sqlite 多线程一起使用。通常,您应该使用 DbContext 作为每个请求的实例,因为 DbContext 不是线程安全的,一次提交不应影响其他提交。

正如 sqlite 网站上提到的,它支持多线程:

SQLite 支持三种不同的线程模式:

单线程。在这种模式下,所有互斥锁都被禁用,SQLite 被禁用 一次在多个线程中使用是不安全的。

多线程。在这种模式下,SQLite 可以被多人安全使用 线程,前提是不使用单个数据库连接 同时在两个或多个线程中。

序列化。在序列化模式下,SQLite 可以被多人安全使用 线程没有限制。

线程模式可以在编译时选择(当 SQLite 库正在从源代码编译)或在开始时(当 打算使用 SQLite 的应用程序正在初始化)或在运行时 (当创建新的 SQLite 数据库连接时)。一般来说 说,运行时覆盖开始时间和开始时间覆盖 编译时。除了,单线程模式不能被覆盖一次 已选中。

默认模式为序列化。

https://www.sqlite.org/threadsafe.html

另外我建议你看看这个SQLite Concurrent Access和这个Can I read and write to a SQLite database concurrently from multiple connections?

根据上面的帖子,sqlite 写入会锁定整个文件,即使是读取也是如此。在互联网上,一些用户建议明确锁定代码以进行写入。

但新版本的 sqlite 有一个名为WAL 的功能。

WAL 模式的第二个优点是写者不会阻塞读者 和读者不要阻止作家。这大多是真的。但是那里 是一些模糊的情况,其中对 WAL 模式数据库的查询可以 返回 SQLITE_BUSY,因此应用程序应该为此做好准备 巧合。

Sqlite 本身表示,即使是多个进程的并发访问也可以由 sqlite 处理。

并根据sqlite.org/faq

如果您的应用程序需要大量并发,那么您 应该考虑使用客户端/服务器数据库。但是经验 表明大多数应用程序需要的并发性远低于它们的 设计师想象。 当 SQLite 试图访问被另一个进程锁定的文件时, 默认行为是返回 SQLITE_BUSY。

可能需要在应用程序本身中处理。

【讨论】:

  • 谢谢!好的,这是否意味着我不需要锁定查询?关于其他问题的任何想法?
  • 是的,谢谢,我阅读了很多似乎没有定论的帖子,但是将每个查询包装在一个 using 语句中,该语句将打开和关闭文件以确保看起来非常昂贵.. 我可以用重试处理 SQLITE_BUSY,这不是问题