1.關於數據庫排序與分頁html
3.LiteDB分頁之漸入佳境github
4.LiteDB的疑問web
5.資源數據庫
在文章:這些.NET開源項目你知道嗎?讓.NET開源來得更加猛烈些吧!(第二輯) 與 .NET平臺開源項目速覽(3)小巧輕量級NoSQL文件數據庫LiteDB中,介紹了LiteDB的基本使用狀況以及部分技術細節,我尚未在實際系統中大量使用,但文章發佈後,有很多網友( loogn)反應在實際項目中使用過,效果還能夠吧。同時也有人碰到了關於LiteDB關於分頁的問題,還不止一個網友,很顯然這個問題從個人思考上來講,做者不可能不支持,同時也翻了一下源碼,發現Find方法有skip和limite參數,直覺告訴我,這就是的。可是網友進一步提問,這個方法並非很好用,它也沒有實現的分頁的狀況。因此就親自操刀,看看究竟是神馬狀況?不看不知道,這個過程還真的不是那麼回事,不過仍是能解決啊。函數
.NET開源目錄:【目錄】本博客其餘.NET開源項目文章目錄學習
本文原文地址:.NET平臺開源項目速覽(7)關於NoSQL數據庫LiteDB的分頁查詢解決過程測試
在實際的項目中,對於關係型數據庫,數據查詢與排序都應該好辦,升序或者降序唄,可是對數據庫的分頁應該不是直接的函數支持,也須要本身的應用程序中進行處理,而後使用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,在大數據面前,若是動不動就查幾千條數據來分頁,也是明顯不合適的。在個人觀點中,要儘可能避免無謂的查詢浪費,也不會有人專門去看幾千甚至幾萬條記錄,若是有,也只是從中找到一部分數據,既然這樣何須不一開始就增長條件,過濾掉那些沒用的數據呢。因此數據庫的那些事,業務的合理性也很重要,數據庫也是機器,他們能力也有限,動不動就仍那麼多沉重的任務給它,也會受不了啊。優化
爲了便於本文的相關代碼演示,咱們使用以下的一個實體類,注意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是默認逐步增長了,主要是爲了測試排序的狀況。
咱們在前面介紹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,知道的朋友接着往下看,別吐槽。解決問題的最終結果可能很簡單,可是過程仍是值得回味的,一步步也是學習和總結優化的過程。
因爲Linq的Take之前不知道,全部走了一些彎路,同時LiteDB的Find方法中的重載函數之一,skip參數也有一些問題,下一節講到具體問題。
考慮到相似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不能獲得
後來發現了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);
}
}
|
結果以下:
和上面是同樣的,但這個顯然要簡潔多了。更加靈活,並且不用降序和升序直接轉換,一次就夠。
根據上面方法,咱們能夠擴展到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遇到的坑。
先看看下面一段普通的代碼,查詢出來的記錄的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的方式也很容易的解決問題,應該差不了多少。
本文的代碼比較簡單,全部代碼都已經貼在上面了。因此就不放具體代碼了,我打算好好把LiteDB的源碼研究一下,爲之後正式的拋棄Sqlite作準備。你們關注博客,若是研究比較深刻,會把相關代碼託管到github。這裏研究還不夠深刻,代碼比較簡單,就省略了吧。