【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 數據庫的話,由於沒有遷移,因此也不存在丟失數據的問題了。

最後再說一句,本文只提供了思路,但實際仍是要看場景來分析。反正無論黑貓仍是白貓,抓到老鼠就是好貓嘛。

相關文章
相關標籤/搜索