【C#】使用EF访问Sqlite数据库

时间:2022-09-20 18:31:48
原文: 【C#】使用EF访问Sqlite数据库

1. 先上Nuget下载对应的包

【C#】使用EF访问Sqlite数据库
如图,搜索System.Data.SQLite下载安装即可,下载完之后带上依赖一共有这么几个:

  1. EntityFramework
  2. System.Data.SQLite
  3. System.Data.SQLite.Core
  4. System.Data.SQLite.EF6
  5. System.Data.SQLite.Linq

安装完成后,会添加App.config文件(如果没有的话),里面添加了一些provider的配置。

2. 先看下DB First模式

如果你用的是VS2017的话,很不幸无法通过“ADO.NET实体数据模型”来生成edmx文件。如果用VS2015及之前版本的话可以去官网下载一个插件,安装之后就可以用了。这里以VS2017为例:
1. 首先,在App.config中配置数据库连接字符串:

<connectionStrings>
    <add name="SqliteTest" connectionString="Data Source=E:\retail.db" providerName="System.Data.SQLite.EF6" />
</connectionStrings>

2.然后就可以 编写数据库上下文和实体了,然后就可以用了

public class RetailContext : DbContext
{
   public RetailContext(): base("SqliteTest"){}     
   public DbSet<Thumbnail> Thumbnails { set; get; }
}
    public class Thumbnail
    {
        public Int64 Id { get; set; }
        [Required]
        [Unique]
        public string OrginFilePath { get; set; }
        [Required]
        public string ThumbnailPath { get; set; }
        [Required]
        public DateTime LastUpdateTime { get; set; }
    }

不过在运行过程中你可能会遇到这个错误SQLite error of “Unable to find the requested .Net Framework Data Provider.
解决方法就是在App.config的providers中添加以下节点

<provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />

完整的如下:

    <providers>
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
      <!-- 1. Solves SQLite error of "Unable to find the requested .Net Framework Data Provider."-->
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
    </providers>

从上面的代码可以看到数据库的路径是写死的不能改变,这样就很不灵活。我喜欢可以动态连接的数据库,所以再次修改:
我们知道DbContext这个类有几个构造函数,一个是接收string类型的connectionString,一般都用的是这个。还有一个构造函数是接收一个DbConnection类型参数和bool类型的参数。这个构造函数就可以实现我们的要求:

  • 首先,构造一个DbConnection类:
    DbConnection sqliteCon = SQLiteProviderFactory.Instance.CreateConnection();
  • 然后,给这个对象设置ConnectionString
    sqliteCon.ConnectionString = dbPath;
  • 最后,把这个对象传给DbContext即可。

    如下:

public class ThumbnailContext : DbContext
    {
        static string dbPath = $"Data Source=E:\\thumbnail.db";
        public static ThumbnailContext Instance
        {
            get
            {
                DbConnection sqliteCon = SQLiteProviderFactory.Instance.CreateConnection();
                sqliteCon.ConnectionString = dbPath;
                return new ThumbnailContext(sqliteCon);
            }
        }
        private ThumbnailContext(DbConnection con) : base(con, true) { }
        public DbSet<Thumbnail> Thumbnails { get; set; }
    }

3. CodeFirst模式

Sqlite默认不支持CodeFirst模式,如果用户因为某些操作删除了我们的db文件,此时我们的程序就不能正常工作了,是不是有点尴尬?不过还是有解决办法的,在Nuget里搜索SQLite.CodeFirst安装即可。
【C#】使用EF访问Sqlite数据库
然后重写DbContextOnModelCreating方法。这里是修改后的数据库上下文:

    public class ThumbnailContext : DbContext
    {
        static string dbPath = $"Data Source={PathManager.AppDataTempThumbnail}\\thumbnail.db";
        public static ThumbnailContext Instance
        {
            get
            {
                DbConnection sqliteCon = SQLiteProviderFactory.Instance.CreateConnection();
                sqliteCon.ConnectionString = dbPath;
                return new ThumbnailContext(sqliteCon);
            }
        }
        private ThumbnailContext(DbConnection con) : base(con, true) { }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //如果不存在数据库,则创建
            Database.SetInitializer(new SqliteCreateDatabaseIfNotExists<ThumbnailContext>(modelBuilder));
        }
        public DbSet<Thumbnail> Thumbnails { get; set; }
    }

4.一些优化

在使用过程中你可能会察觉到,每当第一次访问数据库(查询、插入等)时总是会慢一点,可能有1秒的时间数据库才会做出响应,然后接下再操作就很快。
这是因为当你第一次访问数据库时,EF需要在内存中建立实体与数据库表的映射关系,这个操作需要点时间。所以在你的程序一启动的时候就要把关系给映射好。
这里以上面的ThumbnailContext为例,首先new一个对象,假设为dbContext。然后在你整个应用程序的入口点调用下述代码:

public void Init()
{
   //Pre-Generated Mapping Views
   var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
   var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
   mappingCollection.GenerateViews(new List<EdmSchemaError>());
}

后记:通过EF来访问Sqlite数据库,我也使用了有一段时间了,但是有时候会出现几个莫名奇妙的问题,而且还不太好定位,不知道是不是EF和Sqlite不太兼容的问题。现在尝试改为用ADO.NET来访问,于是就有了接下来的这篇文章:【C#】使用ADO.NET访问Sqlite数据库,SqliteHelper帮助类


参考链接:

1. Sqlite3+EF6踩的坑
2. 让EntityFramework6支持SQLite