關於NoSQL數據庫LiteDB的分頁查詢解決過程

  在文章:這些.NET開源項目你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯) 與 .NET平臺開源項目速覽(3)小巧輕量級NoSQL文件數據庫LiteDB中,介紹了LiteDB的基本使用狀況以及部分技術細節,我尚未在實際系統中大量使用,但文章發佈後,有很多網友( loogn)反應在實際項目中使用過,效果還能夠吧。同時也有人碰到了關於LiteDB關於分頁的問題,還不止一個網友,很顯然這個問題從個人思考上來講,做者不可能不支持,同時也翻了一下源碼,發現Find方法有skip和limite參數,直覺告訴我,這就是的。可是網友進一步提問,這個方法並非很好用,它也沒有實現的分頁的狀況。因此就親自操刀,看看究竟是神馬狀況?不看不知道,這個過程還真的不是那麼回事,不過仍是能解決啊。函數

.NET開源目錄:【目錄】本博客其餘.NET開源項目文章目錄學習

本文原文地址:.NET平臺開源項目速覽(7)關於NoSQL數據庫LiteDB的分頁查詢解決過程測試

1.關於數據庫排序與分頁

    在實際的項目中,對於關係型數據庫,數據查詢與排序都應該好辦,升序或者降序唄,可是對數據庫的分頁應該不是直接的函數支持,也須要本身的應用程序中進行處理,而後使用top或者limite之類的來查詢必定範圍內的數據,做爲一頁,給前臺。例以下面的SQL語句:大數據

1
2
  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的問題咱們在前面一篇文章中已經說過了,默認是自增的,不須要處理。加進來是爲了方便查詢和分頁。實體類基本代碼以下:

1
2
3
4
5
6
7
8
9
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數據庫文件。咱們只拿這些數據作測試。至於之後大數據的查詢以及分頁效率問題,暫時不考慮,咱們只單獨處理分頁的狀況。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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方法,因此選擇它來完成具體數據的選擇,至關於每次都選擇最後幾條。看代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//打開或者建立新的數據庫
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的分頁。沒有上面那麼麻煩了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//根據頁面號直接獲取
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中增長下面的方法,詳細看註釋:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// <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);
     }
}

下面仍是使用上面的例子,直接進行調用:

1
2
3
4
5
6
7
8
9
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的變化狀況,沒有排序:

1
2
3
4
5
6
7
8
9
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);
}

結果以下:

1
2 , 12 , 14 , 16 , 18 , 20 , 4 , 6 , 8 , 10 ,

    是否是很奇怪?沒有想象的是按照順序輸出。因此這個坑花了我好長時間,怎麼試就是不行,既然這樣的話,那麼使用LiteDB自帶的下面這個方法:

1
public  IEnumerable<T> Find(Expression<Func<T,  bool >> predicate,  int  skip = 0,  int  limit =  int .MaxValue)

    就有問題。這個方法skip的是按照上述順序的。因此追根到底,仍是由於直接的使用排序的方法?這裏打個問號吧,說不定有,我沒找到。若是有人比較熟悉的,能夠告知一下,很是感謝。可是使用linq的方式也很容易的解決問題,應該差不了多少。

5.資源

    本文的代碼比較簡單,全部代碼都已經貼在上面了。因此就不放具體代碼了,我打算好好把LiteDB的源碼研究一下,爲之後正式的拋棄Sqlite作準備。你們關注博客,若是研究比較深刻,會把相關代碼託管到github。這裏研究還不夠深刻,代碼比較簡單,就省略了吧。

相關文章
相關標籤/搜索