.NET平臺開源專案速覽(7)關於NoSQL資料庫LiteDB的分頁查詢解決過程
在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯) 與 .NET平臺開源專案速覽(3)小巧輕量級NoSQL檔案資料庫LiteDB中,介紹了LiteDB的基本使用情況以及部分技術細節,我還沒有在實際系統中大量使用,但文章釋出後,有不少網友( )反應在實際專案中使用過,效果還可以吧。同時也有人碰到了關於LiteDB關於分頁的問題,還不止一個網友,很顯然這個問題從我的思考上來說,作者不可能不支援,同時也翻了一下原始碼,發現Find方法有skip和limite引數,直覺告訴我,這就是的。但是網友進一步提問,這個方法並不是很好用,它也沒有實現的分頁的情況。所以就親自操刀,看看到底是神馬情況?不看不知道,這個過程還真的不是那麼回事,不過還是能解決啊。
1.關於資料庫排序與分頁
在實際的專案中,對於關係型資料庫,資料查詢與排序都應該好辦,升序或者降序唄,但是對資料庫的分頁應該不是直接的函式支援,也需要自己的應用程式中進行處理,然後使用top或者limite之類的來查詢一定範圍內的資料,作為一頁,給前臺。例如下面的SQL語句:
Select top PageSize * from TableA where Primary_Key not in (select top (n-1)*PageSize Primary_Key from TableA )
資料的分頁過程中,我們也看到在根據指定條件查詢後,就是記錄集的篩選,所以對於NoSQL資料庫來說,因為沒有了SQL,這些問題不會像常規關係型資料庫那麼突出,畢竟你選擇了NoSQL,在大資料面前,如果動不動就查幾千條資料來分頁,也是明顯不合適的。在我的觀點中,要儘量避免無謂的查詢浪費,也不會有人專門去看幾千甚至幾萬條記錄,如果有,也只是從中找到一部分資料,既然這樣何必不一開始就增加條件,過濾掉那些沒用的資料呢。所以資料庫的那些事,業務的合理性也很重要,資料庫也是機器,他們能力也有限,動不動就仍那麼多沉重的任務給它,也會受不了啊。
2.LiteDB的查詢排序
2.1 測試前準備工作
為了便於本文的相關程式碼演示,我們使用如下的一個實體類,注意Id的問題我們在前面一篇文章中已經說過了,預設是自增的,不需要處理。加進來是為了方便查詢和分頁。實體類基本程式碼如下:
public class Customer { /// <summary>自增Id,編號</summary> public int Id { get; set; } /// <summary>年齡</summary> public int Age { get; set; } /// <summary>姓名</summary> public string Name { get; set; } }
然後我們使用如下的方法插入20條記錄,注意該函式是資料初始化,只需要執行一次即可。會在bin目錄生成Sample資料庫檔案。我們只拿這些資料做測試。至於以後大資料的查詢以及分頁效率問題,暫時不考慮,我們只單獨處理分頁的情況。
static void InitialDB() { //開啟或者建立新的資料庫 using (var db = new LiteDatabase("sample.db")) { //獲取 customers 集合,如果沒有會建立,相當於表 var col = db.GetCollection<Customer>("customers"); for (int i = 0; i < 20; i++) { //建立 customers 例項 var customer = new Customer { //名字迴圈改變 Name = i % 2 == 1 ? "Jim1_" + i.ToString() : "Jim2" + i.ToString(), Age = i, }; // 將新的物件插入到資料表中,Id是自增,自動生成的 col.Insert(customer); } } }
上面的Name是交替改變的,Jim1和Jim2加上編號,而Age是預設逐步增加了,主要是為了測試排序的情況。
2.2 基本查詢與分頁問題
我們在前面介紹LiteDB的基礎文章。。中,對基本查詢做了介紹。方法很靈活。針對上面的例子,我們假設一個查詢分頁的需求:
查Customer表中,Name以"Jim1"開頭的人集合,按Age降序排列,每3條記錄一頁,列印每一頁的Age列表。
針對上面問題,我們需要先簡單分析一下問題:
1.查詢獲取記錄的總數,可以使用Find或者Count方法直接獲取;
2.查詢條件的是Name,可以使用Linq或者Query來進行;
3.由於LiteDB是NoSQL的,所以不支援內部直接排序了,只能使用Linq的OrderBy或者OrderByDescending了;
4.關於分頁,還是選擇和SQL資料庫型別的方法,使用linq的skip方法來跳過一些記錄。這裡留個疑問,因為自己技術有限,平時也只使用基本的linq操作,所以只想到了Skip,知道的朋友接著往下看,別吐槽。解決問題的最終結果可能很簡單,但是過程還是值得回味的,一步步也是學習和總結優化的過程。
3.LiteDB分頁之漸入佳境
由於Linq的Take以前不知道,所有走了一些彎路,同時LiteDB的Find方法中的過載函式之一,skip引數也有一些問題,下一節講到具體問題。
3.1 第一次小試牛刀
考慮到類似SQL的limite和top查詢,我們也在LiteDB中使用這種方式。由於Linq有一個Skip方法,所以選擇它來完成具體資料的選擇,相當於每次都選擇最後幾條。看程式碼:
//開啟或者建立新的資料庫 using (var db = new LiteDatabase("sample.db")) { //獲取 customers 集合,如果沒有會建立,相當於表 var col = db.GetCollection<Customer>("customers"); //1.計算總的數量 var totalCount = col.Count(Query.StartsWith("Name", "Jim1")); //2.計算總的分頁數量 Int32 pageSize = 3 ;//每一頁的數量 var pages = (int)Math.Ceiling((double)totalCount / (double)pageSize); //3.迴圈獲取每一頁的資料 Int32 current = int.MaxValue; for (int i = 0; i < pages; i++) { //查詢條件,附加了Id的範圍,第一次是最大,後面進行更新 var data = col.Find(n => n.Name.StartsWith("Jim1") && n.Id < current) .OrderBy(n => n.Age) //要求是降序,由於要選擇最後的,只能先升序 .Skip(totalCount - (i + 1) * pageSize)//跳過前面頁的記錄 .OrderByDescending(n => n.Age); //降序排列 current = data.Last().Id;//更新當前查到的最大Id //把Id按照頁的順序打印出來 String res = String.Empty; foreach (var item in data.Select(n => n.Age)) res += (item.ToString() + " , "); Console.WriteLine(res); } }
結果如下:
最後1也只有1條記錄,總共10條記錄也是正常的,總共20條,交替插入的。缺點有幾個:
1.效率比較低,每次都選最後的
2.只能從第1頁獲取,不能獲取單獨頁的,因為上一次的Id不能得到
3.2 完全使用Linq分頁
後來發現了Take方法,雖然我猜測應該有,但苦於自己疏忽,導致尋找的時候錯過了,後來自己打算重新寫一個的時候,又去確認一遍的時候才發現。因為skip都可以實現,沒道理Take不實現啊,原理都是一樣的。如果實現也很簡單的。那看看改進版的基於Linq的分頁。沒有上面那麼麻煩了:
//根據頁面號直接獲取 static void SplitPageByPageIndex(int index) { using (var db = new LiteDatabase("sample.db")) { var col = db.GetCollection<Customer>("customers"); //1.計算總的數量 var totalCount = col.Count(Query.StartsWith("Name", "Jim1")); //2.計算總的分頁數量 Int32 pageSize = 3;//每一頁的數量 var pages = (int)Math.Ceiling((double)totalCount / (double)pageSize); //查詢條件 var data = col.Find(n => n.Name.StartsWith("Jim1")) .OrderByDescending(n => n.Age)//降序 .Skip(index * pageSize) //跳過前面頁數數量的記錄 .Take(pageSize); //選擇前面的記錄作為當前頁 //把id按照順序打印出來 String res = String.Empty; foreach (var item in data.Select(n => n.Age)) res += (item.ToString() + " , "); Console.WriteLine(res); } }
結果如下:
和上面是一樣的,但這個顯然要簡潔多了。更加靈活,而且不用降序和升序直接轉換,一次就夠。
3.3 終極解決之擴充套件分頁方法
根據上面方法,我們可以擴充套件到LiteDB中去,雖然我一直認為這一點可以做到,但是研究了很久的原始碼,測試一直不成功,詳細內容第4節介紹。
我選擇直接在原始碼裡面擴充套件,當然也可以單獨寫一個擴充套件方法,不過原始碼裡面更好用,相當於給Find增加一個過載方法,我們在原始碼的Find.cs中增加下面的方法,詳細看註釋:
/// <summary>分頁獲取記錄</summary> /// <typeparam name="TOder">排序欄位型別</typeparam> /// <param name="predicate">linq查詢表示式</param> /// <param name="orderSelector">排序表示式</param> /// <param name="isDescending">是否降序,true降序</param> /// <param name="pageSize">每頁大小</param> /// <param name="pageIndex">要獲取的頁碼,從1開始</param> /// <returns>分頁後的資料</returns> public IEnumerable<T> FindBySplitePage<TOder>(Expression<Func<T, bool>> predicate, Func<T, TOder> orderSelector, Boolean isDescending, int pageSize, int pageIndex) { var allCount = Count(predicate);//計算總數 var pages = (int)Math.Ceiling((double)allCount / (double)pageSize);//計算頁碼 if (pageIndex > pages) throw new Exception("頁面數超過預期"); if (isDescending)//降序 { return Find(predicate) .OrderByDescending(orderSelector) .Skip((pageIndex - 1) * pageSize) .Take(pageSize); } else //升序 { return Find(predicate) .OrderBy(orderSelector) .Skip((pageIndex - 1) * pageSize) .Take(pageSize); } }
下面還是使用上面的例子,直接進行呼叫:
var db = new LiteDatabase("sample.db"); var col = db.GetCollection<Customer>("customers"); //取第二頁,降序 var data = col.FindBySplitePage<Int32>(n => n.Name.StartsWith("Jim1"), n => n.Age, true, 3, 2).ToList(); //把id按照順序打印出來 String res = String.Empty; foreach (var item in data.Select(n => n.Age)) res += (item.ToString() + " , "); Console.WriteLine(res); Console.WriteLine("任務完成");
結果如下,呼叫總體比較簡單,直接使用linq,輸入頁面數量和頁碼就可以了。當然不需要排序也可以,大家可以根據實際情況優化一下。
到這裡,分頁的問題基本是解決了,但還得說一下研究LiteDB遇到的坑。
4.LiteDB的疑問
先看看下面一段普通的程式碼,查詢出來的記錄的Id的變化情況,沒有排序:
using (var db = new LiteDatabase("sample.db")) { var col = db.GetCollection<Customer>("customers"); var data = col.Find(n => n.Name.StartsWith("Jim1"));//普通查詢 //把Id按照頁的順序打印出來 String res = String.Empty; foreach (var item in data.Select(n => n.Id)) res += (item.ToString() + " , "); Console.WriteLine(res); }
結果如下:
2 , 12 , 14 , 16 , 18 , 20 , 4 , 6 , 8 , 10 ,
是不是很奇怪?沒有想象的是按照順序輸出。所以這個坑花了我好長時間,怎麼試就是不行,既然這樣的話,那麼使用LiteDB自帶的下面這個方法:
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate, int skip = 0, int limit = int.MaxValue)
就有問題。這個方法skip的是按照上述順序的。所以追根到底,還是因為直接的使用排序的方法?這裡打個問號吧,說不定有,我沒找到。如果有人比較熟悉的,可以告知一下,非常感謝。但是使用linq的方式也很容易的解決問題,應該差不了多少。
5.資源
本文的程式碼比較簡單,所有程式碼都已經貼在上面了。所以就不放具體程式碼了,我打算好好把LiteDB的原始碼研究一下,為以後正式的拋棄Sqlite做準備。大家關注部落格,如果研究比較深入,會把相關程式碼託管到github。這裡研究還不夠深入,程式碼比較簡單,就省略了吧。
相關推薦
.NET平臺開源專案速覽(7)關於NoSQL資料庫LiteDB的分頁查詢解決過程
在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯) 與 .NET平臺開源專案速覽(3)小巧輕量級NoSQL檔案資料庫LiteDB中,介紹了LiteDB的基本使用情況以及部分技術細節,我還沒有在實際系統中大量使用,但文章釋出後,有不少網友( )反應在實際專案中使用過,效果還
.NET平臺開源專案速覽(3)小巧輕量級NoSQL檔案資料庫LiteDB
今天給大家介紹一個不錯的小巧輕量級的NoSQL檔案資料庫LiteDB。本部落格在2013年也介紹過2款.NET平臺的開源資料庫: 上面2個數據庫我的實際的專案中用過,還不錯。當然資料量很小,主要是客戶比較變態,必須要用xml檔案儲存,就想到了,另外NDatabase只是自己覺得好玩,
.NET平臺開源專案速覽-最快的物件對映元件Tiny Mapper之專案實踐
心情小札:近期換了工作,苦逼於22:00後下班,房間一篇狼藉~ 小翠鄙視到:"你就適合生活在垃圾堆中!!!" 看評論也是挺有價值,同時也看到許多新手同學問道在實際專案中使用的情況。 下面就原作者的程式碼的基礎上略作調整,闡述一下在實際專案場景中的使用: 第一步:瞭解類庫方法:TinyMapper 主
.NET平臺開源專案速覽(14)最快的物件對映元件Tiny Mapper
好久沒有寫文章,工作甚忙,但每日還是關注.NET領域的開源專案。五一休息,放鬆了一下之後,今天就給大家介紹一個輕量級的物件對映工具Tiny Mapper:號稱是.NET平臺最快的物件對映元件。那就一起看看呢。 臨時更新:感謝@ 的意見,為了避免新手誤解,這裡說明一下,Tiny Mappe
.NET平臺開源專案速覽(20)Newlife.Core中簡單靈活的配置檔案
如果用知乎,可以關注專欄: 記得5年前開始拼命翻讀X元件的原始碼,特別是XCode,但對Newlife.Core 的東西瞭解很少,最多隻是會用用,而且用到的只是九牛一毛。裡面好用的東西太多了。 最近一年時間,零零散散又學了很多,也瞭解了很多,不會寫那總要學會用吧,今天就給大家介紹裡面非常好用的自定義配置檔
.NET平臺開源專案速覽(19)Power BI神器DAX Studio
PowerBI更新頻繁,已經有點更不上的節奏,一直在關注和學習中,基本的一些操作大概是沒問題,更重要的是注重Power Query,M函式,以及DAX的使用,這才是核心。 上個月研究了DAX的一些語法和公式,發現這玩意看起來簡單,但其實功能非常強大,所以就想和寫程式碼一樣,弄個工具試一下。
.NET平臺開源專案速覽(21)Cron任務排程CronNET
Quartznet大名鼎鼎應該很少有人不知道,相關的開源專案很多,不過那東東對新手來說,有點晦澀,加上哪個Cron表示式,可能一進去雲裡霧裡的。今天給大家介紹一個簡單的在.NET平臺上執行Cron計劃任務的元件CronNET。同時也給大家推介幾個Cron表示式的工具。 1.Cron介紹和工具
.NET平臺開源專案速覽(11)KwCombinatorics排列組合使用案例(1)
今年上半年,我在KwCombinatorics系列文章中,重點介紹了KwCombinatorics元件的使用情況,其實這個元件我5年前就開始用了,非常方便,麻雀雖小五臟俱全。所以一直非常喜歡,才寫了幾篇文章推薦給大家。最近在計算足球彩票結果組合過程中,使用的到了其功能,生成排列,非常具有代表性,而且也
.NET平臺開源專案速覽(1)SharpConfig配置檔案讀寫元件
在.NET平臺日常開發中,讀取配置檔案是一個很常見的需求。以前都是使用System.Configuration.ConfigurationSettings來操作,這個說實話,搞起來比較費勁。不知道大家有沒有同感。所以更多時候我還是喜歡使用開源的東西,更加方便簡潔,也穩定。省去自己的麻煩。今天就介紹一個非常精緻
.NET平臺開源專案速覽(9)軟體序列號生成元件SoftwareProtector介紹與使用
在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯)中,給大家初步介紹了一下Software Protector序列號生成元件。今天就通過一篇簡單的文章來預覽一下其強大的功能。雖然我人為其已經基本滿足了一個軟體序列號的所有要素,但至於大家用不用得上,還得看大家的需求。總的來
.NET平臺開源專案速覽(10)FluentValidation驗證元件深入使用(二)
在上一篇文章:.NET平臺開源專案速覽(6)FluentValidation驗證元件介紹與入門(一) 中,給大家初步介紹了一下FluentValidation驗證元件的使用情況。文章從構建間的驗證器開始,到最後的結果,以及複雜驗證等都做了比較深入的講解和使用。但其實一個完整的元件是麻雀雖小五臟俱全
.NET平臺開源專案速覽(2)Compare .NET Objects物件比較元件
.NET平臺開源專案速覽今天介紹一款小巧強大的物件比較元件。可以更詳細的獲取2個物件的差別,並記錄具體差別,比較過程和要求可以靈活配置。 1.Compare .NET Objects介紹 Compare .NET Objects元件是.NET平臺用於深入比較2個.NET物件的開源元
.NET平臺開源專案速覽(13)機器學習元件Accord.NET框架功能介紹
Accord.NET Framework是在AForge.NET專案的基礎上封裝和進一步開發而來。因為AForge.NET更注重與一些底層和廣度,而Accord.NET Framework更注重與機器學習演算法以及提供計算機視訊、音訊、訊號處理以及統計應用相關的解決方案。該專案使用C#語言編寫,專
.NET平臺開源專案速覽(5)深入使用與擴充套件SharpConfig元件
上個月在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧 和 .NET平臺開源專案速覽(1)SharpConfig配置檔案讀寫元件 中都提到了SharpConfig元件,簡單輕量級,速度快,而且還有比較深入的使用介紹。在文章釋出後,也有網友提到一些問題,當時我也沒仔細去分析,在這次我親
.NET平臺開源專案速覽(8)Expression Evaluator表示式計算元件使用
在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯)中,給大家初步介紹了一下Expression Evaluator驗證元件。那裡只是概述了一下,並沒有對其使用和強大功能做深入研究,所以今天就通過一篇簡單的文章來預覽一下其強大的功能。本文曾在【原創】.NET開源表示式計算元
.NET平臺開源專案速覽(4).NET文件生成工具ADB及使用
很久以前就使用ADB這個工具來生成專案的幫助文件。功能強大,在學習一些開源專案的過程中,官方沒有提供CHM幫助文件,所以為了快速的瞭解專案結構和註釋。就生成文件來自己看,非常好用。這也是一個學習方法吧。例如本文在: 上述2篇文章中最後的資源中就手動製作了CHM幫助文件。有時候我們還可
.NET平臺開源專案速覽(6)FluentValidation驗證元件介紹與入門(一)
在文章:這些.NET開源專案你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯)中,給大家初步介紹了一下FluentValidation驗證元件。那裡只是概述了一下,並沒有對其使用和強大功能做深入研究,所以今天以及接下去的幾篇文章就專門介紹這個元件。不僅僅是它小,輕量級,優雅,而且一直在持續更新中
.NET平臺開源專案速覽(12)雜湊演算法集合類庫HashLib
.NET的System.Security.Cryptography名稱空間本身是提供加密服務,雜湊函式,對稱與非對稱加密演算法等功能。實際上,大部分情況下已經滿足了需求,而且.NET實現的都是目前國際上比較權威的,標準化的演算法,所以還是安全可靠的。但也有一些場合,需要自己實現一些安全雜湊演算法。
.NET平臺開源專案速覽(18)C#平臺JSON實體類生成器JSON C# Class Generator
去年,我在一篇文章用原始方法解析複雜字串,json一定要用JsonMapper麼?中介紹了簡單的JSON解析的問題,那種方法在當時的環境是非常方便的,因為不需要生成實體類,結構很容易解析。但隨著業務的變化,也會碰到超級變態的JSON,如果還按照以前的思路,會把人搞抽風掉,一旦結構變化,又要重來。所
.NET平臺開源專案速覽(16)C#寫PDF檔案類庫PDF File Writer介紹
1年前,我在文章:這些.NET開源專案你知道嗎?.NET平臺開源文件與報表處理元件集合(三)中(第9個專案),給大家推薦了一個開源免費的PDF讀寫元件 PDFSharp,PDFSharp我2年前就看過,用過簡單的例子,不過程式碼沒有寫成專門的文章。最近在查詢資料的時候,又發現一款小巧的寫PDF檔案