【UWP】使用 LiteDB 存储数据
序言:
在 UWP 中,常见的存储数据方式基本上就两种。第一种方案是 UWP 框架提供的 ApplicationData Settings 这一系列的方法,适用于存放比较轻量的数据,例如存个 Boolean 类型的设置项这种是最适合不过的了。另一种方案是用 Sqlite 这种数据库,适合存放数据量大或者结构复杂,又或者需要根据条件查询的场合,例如开发个宝可梦数据查询,或者 Jav 图书馆(咳咳)。
场景分析:
在某些场合,我们很可能是要持久化一个复杂的对象的,例如通过 OAuth 授权成功获取到的用户信息,有可能就类似下面的结构:
{
"id": 1,
"name": "Justin Liu",
"gender": 2,
"location": {
"name": "Melbourne",
"name_cn": "墨尔本"
}
}
又或者做一个 RSS 阅读器,弄个后台服务提前先把数据拉下来,那肯定也要存放起来吧。这相当于要把一个以时间排序为依据的列表进行持久化。
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 库),另一个是序列化和反序列化的性能,特别是对象较大的时候。
小结:
这仅仅只是考虑到上面 user 这种结构的存放,如果是 rss 那种的结构,存放的话, A 方案几乎做不了,B 方案倒是能做。但假如做个查询(例如查询某一天时间范围内的),ApplicationData Settings 方案是解决不了的(当然你说全部加载到内存再查询也行,但这就跟用 EF 全部加载出来再分页一样搞笑)。
Sqlite 方案
Sqlite 方案其实也可以细分两种,一种是直接撸 sql,另一种是用 ef core 这种 orm 框架。撸 sql 这种方案说实话我是没实行过,因为相当的不 awesome,我自己也很多年没写过一行 sql 了(虽然我平时上班是干后端工作的)。这里主要说说 ef core 的方案。
新建一个 .net standard 的项目:

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 用会炸!
然后创建数据库迁移:

接下来 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 的值。
打开的话是这个样子:

然后右键我们的 Article 选择 Query

可以看见出现了类似于我们熟悉的 sql 语句,然后点击 Run 按钮。

可见我们刚才插入进去的数据出现了。
回到需求这边来,假设我们这个 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。

可见数据是已经插入进去了,没有任何的数据库表迁移,相当优雅而且 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();
}

当然还有更多用法,这里就不再介绍了,各位看官可以去看 LiteDB 官方的文档,而且这玩意我觉得算是个较为成熟的东西了。
总结:
本文我觉得更偏向于与各位看官交流经验吧,本文中 ApplicationData Settings 的 A、B 方案其实我项目也都有在使用。特别 A 方案,加载数据的时候需要特别严谨的逻辑,例如其中一个字段没有值该如何处理这种。B 方案则简单 catch 个 JsonSerializationException 一般就没问题了,有时候偷个懒还是挺方便的。而 Sqlite 的方案,说句实话,我目前并没有在项目中用到(之前有一个但后来弃坑了,而且是 DbFirst 而不是本文 CodeFirst 的方式)。所以 Sqlite 方案,我在写本文的时候才发现最新的 3.1.1(理论上 3.x 的都这样)在 UWP 上压根就用不了。
对于本文的主角,LiteDB。我是持乐观态度的(虽然我还没在我项目中用到,下个项目想个办法用上^-^)。一般来说,客户端存放的数据重要性肯定是不高的,重要的肯定都是存到服务端去了。也就是说,客户端的数据更多情况是起到一种缓存一样的作用。例如上面的,假如 user 没数据了,那让用户重新授权一次就好了,这没啥的。对于这些场景来说,关系型数据库就太重了,而且数据库迁移是有可能丢失数据的(这个 ef 创建迁移的时候有提示,但实际上代码肯定要去留意的)。在服务端,如果某行数据缺失字段的话,连上数据库手动补一下就好了。但假如这数据库是在客户机器上,那就头大了。用 LiteDB 这种 nosql 数据库的话,因为没有迁移,所以也不存在丢失数据的问题了。
最后再说一句,本文只提供了思路,但实际还是要看场景来分析。反正不管黑猫还是白猫,抓到老鼠就是好猫嘛。
【UWP】使用 LiteDB 存储数据的更多相关文章
- 用python pickle库来存储数据对象
pickling有一个更常用的叫法是serialization,它是指把python对象转化成字节流byte stream, unpickling就是把byte stream转换成对象.python的 ...
- android开发之存储数据
android数据存储之SharedPreferences 一:SharedPreferences SharedPreferences是Android平台上一个轻量级的存储类,用来保存应用的一些常用配 ...
- Android应用开发SharedPreferences存储数据的使用方法
Android应用开发SharedPreferences存储数据的使用方法 SharedPreferences是Android中最容易理解的数据存储技术,实际上SharedPreferences处理的 ...
- Android使用SharedPreference存储数据
SharedPreference存储数据和文件存储更加方便的一点是可以按照一定的数据类型进行存储,同时取数据时也能够获取到相应的数据类型.它是按照map的方式来存储和读取数据的. MainActivi ...
- Android使用文件存储数据
Android上最基本的存储数据的方式即为使用文件存储数据,使用基本的Java的FileOutStream,BufferedWriter,FileInputStream和BufferedReader即 ...
- Fresco源码解析 - DataSource怎样存储数据
Fresco源码解析 - DataSource怎样存储数据 datasource是一个独立的 package,与FB导入的guava包都在同一个工程内 - fbcore. datasource的类关系 ...
- HashMap存储数据赋值javabean简单示例
package com.shb.web; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** ...
- IOS之分析网易新闻存储数据(CoreData的使用,增删改查)
用过网易新闻客户端的朋友们都知道,获取新闻列表时有的时候他会请求网络有时候不会,查看某条新闻的时候再返回会标注已经查看的效果,接下来分析一下是如何实现的. 首先: 1.网易新闻用CoreData存储了 ...
- Android提供了5种方式存储数据:
--使用SharedPreferences存储数据: --文件存储数据: --SQLite数据库存储数据: --使用ContentProvider存储数据: --网络存储数据: 一:使用SharedP ...
随机推荐
- P1027 三角形的周长
题目描述 有n根棍子,棍子i的长度为Ai.现在想要从中选出3根棍子组成周长尽可能长的三角形.请输出最大周长,若无法组成三角形则输出0. 输入格式 第一行是一个正整数n(3<=n<=1000 ...
- C# Thread.Join();Thread.Abort();
Join() 等待当前线程运行完成后,才继续执行主线程后续代码: Abort() 结束当前线程,继续执行主线程后续代码: Thread.Join(); static void Main(string[ ...
- Date日期时间相关
最近在封装一个关于时间函数的功能时,竟发现这些最基本的函数都有些生疏,于是进来来总结复习下,巩固自己记忆的同时,希望能帮助到需要的人 首先了解下日期对象相关的方法 var date = new Dat ...
- java 嵌入式数据库H2
H2作为一个嵌入型的数据库,它最大的好处就是可以嵌入到我们的Web应用中,和我们的Web应用绑定在一起,成为我们Web应用的一部分.下面来演示一下如何将H2数据库嵌入到我们的Web应用中. 一.搭建测 ...
- Ubuntu14.04虚拟机下基本操作(typical安装)
1.打开终端:ctrl+alt+T 2.图形桌面和命令行界面切换:Ctrl+Alt+F1和Ctrl+Alt+F7 3.切换到root用户:激活前,sudo su+回车: 激活后,su+回车. 切换回 ...
- etcd配置文件详解
一 示例yml配置文件 # This is the configuration file for the etcd server. # Human-readable name for this mem ...
- ML基础——搜索引擎与图书管理,百度与李彦宏
本文始发于个人公众号:TechFlow 谈及机器学习,大家想必会有许多联想,比如最近火热的人工智能,再比如战胜李世石的AlphaGo,甚至还会有人联想起骇客帝国或者是机械公敌等经典机器人电影. 但实际 ...
- java面试-java动态代理和cglib代理
代理模式就是为了提供额外或不同的操作,而插入的用来替代实际对象的对象,这些操作涉及到与实际对象的通信,因此代理通常充当中间人角色 一.java动态代理 java动态代理可以动态地创建代理并动态 ...
- java引用类型的浅拷贝与深拷贝理解
1.浅拷贝 只会复制地址值,也就是同一个对象两个引用,只是复制了一个引用而已. 2.深拷贝 重新在堆里创建一个新对象给新引用,连同地址值也不一样. 首先要知道Object的clone()方法, pub ...
- 1066 图像过滤 (15分)C语言
图像过滤是把图像中不重要的像素都染成背景色,使得重要部分被凸显出来.现给定一幅黑白图像,要求你将灰度值位于某指定区间内的所有像素颜色都用一种指定的颜色替换. 输入格式: 输入在第一行给出一幅图像的分辨 ...