1. 程式人生 > >.NET平臺開源專案速覽(7)關於NoSQL資料庫LiteDB的分頁查詢解決過程

.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檔案