【UWP】使用 LiteDB 存储数据

序言:

在 UWP 中,常见的存储数据方式基本上就两种。第一种方案是 UWP 框架提供的 ApplicationData Settings 这一系列的方法,适用于存放比较轻量的数据,例如存个 Boolean 类型的设置项这种是最适合不过的了。另外一种方案是用 Sqlite 这种数据库,适合存放数据量大或者结构复杂,又或者须要根据条件查询的场合,例如开发个宝可梦数据查询,或者 Jav 图书馆(咳咳)。git

场景分析:

在某些场合,咱们极可能是要持久化一个复杂的对象的,例如经过 OAuth 受权成功获取到的用户信息,有可能就相似下面的结构:github

{
    "id": 1,
    "name": "Justin Liu",
    "gender": 2,
    "location": {
        "name": "Melbourne",
        "name_cn": "墨尔本"
    }
}

又或者作一个 RSS 阅读器,弄个后台服务提早先把数据拉下来,那确定也要存放起来吧。这至关于要把一个以时间排序为依据的列表进行持久化。sql

ApplicationData Settings 方案

在以上两种场合,用 ApplicationData Settings 解决起来多是比较快速的。以第一种状况来讲,又能够细分两种存储方案。数据库

A 方案,分字段存放:

ApplicationData.Current.LocalSettings.Values["user.id"] = user.Id;
ApplicationData.Current.LocalSettings.Values["user.name"] = user.Name;
ApplicationData.Current.LocalSettings.Values["user.gender"] = user.Gender;
ApplicationData.Current.LocalSettings.Values["user.location.name"] = user.Location.Name;
ApplicationData.Current.LocalSettings.Values["user.location.name_cn"] = user.Location.NameCn;

固然调用 ApplicationData.Current.LocalSettings.CreateContainer 拿个 Container 来存也是能够。(或者说这样更好一点)后端

这样存储的话,能够按需更新,也能够按需加载,例如我只须要用户的名字那就只加载名字好了。缓存

但这方案缺点也很多,一个是假如上面的 Gender 是一个枚举,那这段代码就炸了。ApplicationData Settings 是不能直接存储枚举类型的,须要处理一下(通常转数值存,不建议转字符串存)。另外一个若是字段多的话,代码行数也跟着变多,就很容易就会写错。但实际上若是字段少的话,这方案是至关合适的。框架

B 方案,序列化存放:

ApplicationData.Current.LocalSettings.Values["user"] = JsonConvert.SerializeObject(user);

提及序列化,那第一时间确定是想到 JSON 了。这方案解决了 A 方案的痛点,但按需加载、按需更新就没法实现了。这个方案胜在泛用,包括 Windows Community Toolkit 也是这么作的。但在我看来,这个方案只知足了需求,但不够优秀。一是依赖了 JSON.net(或是别的 JSON 库),另外一个是序列化和反序列化的性能,特别是对象较大的时候。less

小结:

这仅仅只是考虑到上面 user 这种结构的存放,若是是 rss 那种的结构,存放的话, A 方案几乎作不了,B 方案却是能作。但假如作个查询(例如查询某一天时间范围内的),ApplicationData Settings 方案是解决不了的(固然你说所有加载到内存再查询也行,但这就跟用 EF 所有加载出来再分页同样搞笑)。nosql

Sqlite 方案

Sqlite 方案其实也能够细分两种,一种是直接撸 sql,另外一种是用 ef core 这种 orm 框架。撸 sql 这种方案说实话我是没实行过,由于至关的不 awesome,我本身也不少年没写过一行 sql 了(虽然我平时上班是干后端工做的)。这里主要说说 ef core 的方案。ide

新建一个 .net standard 的项目:

Snipaste_2020-01-20_11-07-07

Article.cs 以下:

public class Article
{
    public int Id { get; set; }

    public string Title { get; set; }

    public string Content { get; set; }

    public DateTime PublishTime { get; set; }
}

ApplicationDbContext.cs 以下:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext()
    {
    }

    public DbSet<Article> Articles { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.UseSqlite("Data Source=articles.db");
    }
}

而且项目引用 Microsoft.EntityFrameworkCore.Sqlite 包,注意是用 2.2.6 版本的,3.x 的 UWP 用会炸!

而后建立数据库迁移:

Snipaste_2020-01-20_11-07-07

接下来 UWP 项目引用该 .net standard 库,修改 App.xaml.cs 的代码:

public App()
{
    using (var context = new ApplicationDbContext())
    {
        context.Database.Migrate();
    }

    this.InitializeComponent();
    this.Suspending += OnSuspending;
}

这样启动程序的时候就会执行数据库迁移了,接下来程序代码里就可使用了。

由于主题并非 Sqlite,这里我就仅仅以 demo 级的态度代码来举例子,并且园子里也有大牛写过相关更详细的博文。

小结:

Sqlite 方案看似很好,但我认为缺点也是有的。一个是该方案过重了,关系型数据库意味着就是有数据表,像我只是要存个 user 的信息的话,come on,能 easy 一点吗?另外一个就是 ef core 对 UWP 的支持度,上面我也说了,3.x 的是会炸掉的。PS 一句,微软对 UWP 目前不是很上心,System.Text.Json 这个包,4.7.0 版本在 UWP 上要用就得写 rd.xml,但 4.6.0 就不须要。总结一点,Sqlite 方案就是把牛刀,而咱们目前是要杀鸡。

分析:

那有没有介于 ApplicationData Settings 和 Sqlite 之间的方案呢?Sqlite 是关系型数据库,嗯,意味着 sql。说到 sql,就想到 nosql,就想到 MongoDb、Redis 这些玩意。事实上,因为这些 nosql 数据库都是 schema less 的,也就是不存在表结构,增删字段就没有说改动表结构这么一说,所以是至关适合用在一些非关键性数据的存储当中的。可是 MongoDb、Redis 这些玩意都是须要一个 server 端,而 UWP 确定不可能带个 server 的,那么有没有相似于 Sqlite 这种单文件无 server 的,并且 UWP 能用的呢,固然最好的话仍是用 .net 开发的。我找了一下,还真被我找着了,接下来就是本文的主角 —— LiteDB。

正文:

LiteDB 官方主页:https://www.litedb.org/

Github:https://github.com/mbdavid/LiteDB

在咱们的 UWP 项目中添加 LiteDB 的引用。(注意本文使用 5.0.0-rc 版本,由于须要对应下文的 LiteDB Studio 使用)

以插入数据为例:

var dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "MyArticles.db");
using (var db = new LiteDatabase(dbPath))
{
    var articles = db.GetCollection<Article>();
    
    var article = new Article
    {
        Title = "test title",
        Content = "test content",
        PublishTime = DateTime.Now
    };

    articles.Insert(article);
}

数据库路径须要设定一下,放在 UWP 的用户数据文件夹下面。不然会放到 Appx 文件夹,而这个路径是没有写入权限的。

db.GetCollection<Article>() 这一句至关于在该数据库中建立了一个文档(假如不存在),用 Sqlite 的概念来讲就是建立了一个表。名字就叫 Article,固然也能够经过该方法的重载来设置名字。Id 咱们不须要进行设定,这个跟 EF 是同样的,默认是会自动递增的。而后咱们来看看咱们的数据是否插入成功。下载 LiteDB Studio(我下载的是 0.9 版本):

https://github.com/mbdavid/LiteDB.Studio/releases

下载以后打开咱们的数据库,数据库的路径能够给上面代码打个断点拿到 dbPath 的值。

打开的话是这个样子:

Snipaste_2020-01-20_13-26-07

而后右键咱们的 Article 选择 Query

Snipaste_2020-01-20_13-27-08

能够看见出现了相似于咱们熟悉的 sql 语句,而后点击 Run 按钮。

Snipaste_2020-01-20_13-28-01

可见咱们刚才插入进去的数据出现了。

 

回到需求这边来,假设咱们这个 Article 类某天多了个字段,例如文章做者。修改 Article.cs:

public class Article
{
    public int Id { get; set; }

    public string Title { get; set; }

    public string Content { get; set; }

    public string Owner { get; set; }

    public DateTime PublishTime { get; set; }
}

修改咱们的插入数据代码:

var dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "MyArticles.db");
using (var db = new LiteDatabase(dbPath))
{
    var articles = db.GetCollection<Article>();
    
    var article = new Article
    {
        Title = "test title",
        Content = "test content",
        Owner = "Justin Liu",
        PublishTime = DateTime.Now
    };

    articles.Insert(article);
}

再次看看咱们的 LiteDB Studio。

Snipaste_2020-01-20_13-47-48

可见数据是已经插入进去了,没有任何的数据库表迁移,至关优雅并且 easy。

 

要作查询的话也简单:

var dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "MyArticles.db");
using (var db = new LiteDatabase(dbPath))
{
    var articles = db.GetCollection<Article>();

    var list = articles
        .Find(temp => temp.PublishTime >= DateTime.Parse("2020-01-20 13:00:00"))
        .ToList();
}

Snipaste_2020-01-20_13-54-06

固然还有更多用法,这里就再也不介绍了,各位看官能够去看 LiteDB 官方的文档,并且这玩意我以为算是个较为成熟的东西了。

 

总结:

本文我以为更偏向于与各位看官交流经验吧,本文中 ApplicationData Settings 的 A、B 方案其实我项目也都有在使用。特别 A 方案,加载数据的时候须要特别严谨的逻辑,例如其中一个字段没有值该如何处理这种。B 方案则简单 catch 个 JsonSerializationException 通常就没问题了,有时候偷个懒仍是挺方便的。而 Sqlite 的方案,说句实话,我目前并无在项目中用到(以前有一个但后来弃坑了,并且是 DbFirst 而不是本文 CodeFirst 的方式)。因此 Sqlite 方案,我在写本文的时候才发现最新的 3.1.1(理论上 3.x 的都这样)在 UWP 上压根就用不了。

对于本文的主角,LiteDB。我是持乐观态度的(虽然我还没在我项目中用到,下个项目想个办法用上^-^)。通常来讲,客户端存放的数据重要性确定是不高的,重要的确定都是存到服务端去了。也就是说,客户端的数据更多状况是起到一种缓存同样的做用。例如上面的,假如 user 没数据了,那让用户从新受权一次就行了,这没啥的。对于这些场景来讲,关系型数据库就过重了,并且数据库迁移是有可能丢失数据的(这个 ef 建立迁移的时候有提示,但实际上代码确定要去留意的)。在服务端,若是某行数据缺失字段的话,连上数据库手动补一下就行了。但假如这数据库是在客户机器上,那就头大了。用 LiteDB 这种 nosql 数据库的话,由于没有迁移,因此也不存在丢失数据的问题了。

最后再说一句,本文只提供了思路,但实际仍是要看场景来分析。反正无论黑猫仍是白猫,抓到老鼠就是好猫嘛。

相关文章
相关标签/搜索