重用Entity Framework Sqlite DBContext是否安全?

时间:2018-12-18 08:05:45

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

我做类似的事情:

  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. 我需要打{{1​​}}吗?似乎没有它就可以正常工作,但是我已经看过上面提到的帖子了吗?

  5. Sqlite是否需要Database.EnsureCreated()?

谢谢!

2 个答案:

答案 0 :(得分:1)

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

正如sqlite网站上提到的,它支持mutltithreading:

  

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 writes甚至锁定了整个文件的读取。在互联网上,一些用户建议明确锁定代码以进行写操作。

但是新版本的sqlite具有称为WAL的功能。

  

WAL模式的第二个优点是作者不会阻止读者   和读者不要阻挡作家。这基本上是对的。但是那里   在一些晦涩的情况下,针对WAL模式数据库的查询可以   返回SQLITE_BUSY,因此应为此准备应用程序   偶然。

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

根据sqlite.org/faq

  

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

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

答案 1 :(得分:1)

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

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

如果您的线程现在和将来将是唯一的用户,则重用DbContext不会有太大危害。但是,请记住,在处置dbContext之前,不能保证确实写入了数据。此外:您的dbContext会将所有提取的数据保留在本地内存中,因此过一会儿您会将完整的数据库保存在本地内存中。

考虑使用存储库模式,您可以在其中隐藏数据的持久存储方式,该存储库模式可以更多地了解存储库的用途,并且可以更明智地决定要保留哪些数据以及要查询哪些数据新的dbContext创建数据库。

例如,如果您有一个包含学校,学生和教师的数据库,并且经常查询他们的数据,但是很少查询退休教师的数据和研究生的数据,则存储库可以保留所有获取的未退休/已毕业的数据老师/学生在内存中,并且仅创建一个新的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打开还将把所有获取的数据保留在内存中。