前言:項目中又要用到一個四級分類數據表,以前我曾經在這方面按步就班玩過很多CRUD的操做,感受工做內容有很多重複,有必要再總結一下,對新手可能會有點幫助,同時以備本身往後再用。html
一、數據表設計web
開門見山,Category表設計以下:sql
數據表字段簡單說明:數據庫
列名 | 數據類型 | 默認值 | 備註 |
Id | int | 自增主鍵 | |
Name | varchar(256) | 分類類別名稱 | |
ParentId | int | 0 | 父母分類Id |
Depth | int | 1 | 深度,從1遞增 |
Status | int | 0 | 狀態:0禁用,1啓用 |
Priority | int | 0 | 優先級,越大,同級顯示的時候越靠前 |
說明:在設計實現這個數據表以前,我搜索參考並比較了一下其餘無限層級設計方案,好比這一篇和這一篇,雖然本文最終使用了最多見的層級設計而沒有采納另外的幾種方法,可是不能否認它們對開闊設計思路是頗有啓發的。緩存
二、簡單查詢函數
(1)一般,在實際應用中簡單查詢某一級別可用(Status等於1)的分類很是簡單:spa
1
2
3
4
5
6
7
8
|
SELECT
[Id]
,[
Name
]
,[ParentId]
,[Depth]
,[Status]
,[Priority]
FROM
[Category](NOLOCK)
WHERE
Status=1
AND
Depth=n
--n>=1
|
最後按照優先級(Priority)字段逆序便可。設計
(2)當須要按照某一個Id查找它及它的全部子級或者父級成員,避開遞歸,直接寫sql查詢會比較難如下手,並且Sql Server2005以前的版本還須要用到臨時表,處理起來不是那麼直觀。自從Sql Server2005/2008橫空出世,利用With語句可用很是輕鬆地寫出查詢,下面貼兩個開發中常常用到的查詢存儲過程(Sql Server2005/2008支持):code
a、按照某一個Id查詢它及它的全部子級成員存儲過程htm
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
30
31
32
33
34
35
36
37
38
39
40
|
CREATE
PROCEDURE
[dbo].[sp_GetChildCategories] (@Id
int
)
AS
BEGIN
WITH
Record
AS
(
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Category(NOLOCK)
WHERE
Id=@Id
UNION
ALL
SELECT
a.Id Id,
a.
Name
Name
,
a.ParentId ParentId,
a.Depth Depth,
a.Status Status,
a.Priority Priority
FROM
Category(NOLOCK) a
JOIN
Record b
ON
a.ParentId=b.Id
)
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Record
WHERE
Status=1
ORDER
BY
Priority
DESC
END
|
b、按照某一個Id查詢它及它的全部父級成員存儲過程
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
30
31
32
33
34
35
36
37
38
39
40
|
CREATE
PROCEDURE
[dbo].[sp_GetParentCategories] (@Id
int
)
AS
BEGIN
WITH
Record
AS
(
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Category(NOLOCK)
WHERE
Id=@Id
UNION
ALL
SELECT
a.Id Id,
a.
Name
Name
,
a.ParentId ParentId,
a.Depth Depth,
a.Status Status,
a.Priority Priority
FROM
Category(NOLOCK) a
JOIN
Record b
ON
a.Id=b.ParentId
)
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Record
WHERE
Status=1
ORDER
BY
Priority
DESC
END
|
分析上面兩個存儲過程,實際上,您也能夠提取出下面的兩段sql語句直接代替上面的查詢存儲過程:
c、按照某一個Id查詢它及它的全部子級成員sql語句
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
30
31
32
33
34
35
|
WITH
Record
AS
(
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Category(NOLOCK)
WHERE
Id=@Id
--@Id是外部傳入的參數
UNION
ALL
SELECT
a.Id Id,
a.
Name
Name
,
a.ParentId ParentId,
a.Depth Depth,
a.Status Status,
a.Priority Priority
FROM
Category(NOLOCK) a
JOIN
Record b
ON
a.ParentId=b.Id
)
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Record
WHERE
Status=1
ORDER
BY
Priority
DESC
|
d、按照某一個Id查詢它及它的全部父級成員sql語句
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
30
31
32
33
34
35
|
WITH
Record
AS
(
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Category(NOLOCK)
WHERE
Id=@Id
--@Id是外部傳入的參數
UNION
ALL
SELECT
a.Id Id,
a.
Name
Name
,
a.ParentId ParentId,
a.Depth Depth,
a.Status Status,
a.Priority Priority
FROM
Category(NOLOCK) a
JOIN
Record b
ON
a.Id=b.ParentId
--匹配關係
)
SELECT
Id,
Name
,
ParentId,
Depth,
Status,
Priority
FROM
Record
WHERE
Status=1
ORDER
BY
Priority
DESC
|
參數@Id毫無疑問,是你須要在外部程序裏傳入的參數。選擇存儲過程或者直接使用sql語句看本身的喜愛(我的傾向於寫sql語句)。
三、項目實踐經驗之談
在實際項目中,對於分類表,一般都會作相應的緩存(這種類型的數據一般說多也很少,說少也很多,可是相對比較穩定),總結一下我在web項目中的使用經驗(經驗之談,請務必當心甄別取捨):
(1)、一次性取出數據庫中全部可用分類類別數據;
(2)、數據(Category表數據)轉換成對應實體Category;
a、Category實體類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
using
System;
/// <summary>
/// 分類實體
/// </summary>
[Serializable]
public
class
Category : BaseCategory
//繼承自BaseCategory
{
public
int
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
int
ParentId {
get
;
set
; }
public
int
Depth {
get
;
set
; }
public
int
Status {
get
;
set
; }
public
int
Priority {
get
;
set
; }
}
|
咱們看到,Category實體繼承自BaseCategory類,這個類咱們定義以下:
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
30
31
32
33
34
35
36
37
38
|
public
abstract
class
BaseCategory : DotNet.Common.Model.PagerBase
//PagerBase 分頁基類
{
/// <summary>
/// 一級分類id
/// </summary>
public
int
FirstCategoryId {
get
;
set
; }
/// <summary>
/// 一級分類名
/// </summary>
public
string
FirstCategoryName {
get
;
set
; }
/// <summary>
/// 二級分類id
/// </summary>
public
int
SecondCategoryId {
get
;
set
; }
/// <summary>
/// 二級分類名
/// </summary>
public
string
SecondCategoryName {
get
;
set
; }
/// <summary>
/// 三級分類id
/// </summary>
public
int
ThirdCategoryId {
get
;
set
; }
/// <summary>
/// 三級分類名
/// </summary>
public
string
ThirdCategoryName {
get
;
set
; }
/// <summary>
/// 四級分類id
/// </summary>
public
int
ForthCategoryId {
get
;
set
; }
/// <summary>
/// 四級分類名
/// </summary>
public
string
ForthCategoryName {
get
;
set
; }
}
|
b、接着經過必定的方法或函數,對Category實體類再作一些處理,完善它的層級關係。好比經過遞歸函數,初始化一次,準備好這些有層級的數據實體:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
/// <summary>
/// 分類實用幫助類
/// </summary>
public
class
CategoryUtil
{
/// <summary>
/// 分層級的數據實體字典 key: Id value:分類實體
/// </summary>
public
static
IDictionary<
int
, Category> DictCategories {
get
;
set
; }
static
CategoryUtil()
{
Init();
}
/// <summary>
/// 根據品類類別構造一個適合查找的dictionary(1~4級品類ID和對應名稱)
/// </summary>
private
static
void
Init()
{
//DictProductTypes=//查庫,一次取出全部可用分類數據 to do
foreach
(KeyValuePair<
int
, Category> kv
in
DictCategories)
{
Category model = kv.Value;
switch
(model.Depth)
{
default
:
break
;
case
1:
model.FirstCategoryId = model.Id;
model.FirstCategoryName = model.Name;
break
;
case
2:
model.SecondCategoryId = model.Id;
model.SecondCategoryName = model.Name;
break
;
case
3:
model.ThirdCategoryId = model.Id;
model.ThirdCategoryName = model.Name;
break
;
case
4:
model.ForthCategoryId = model.Id;
model.ForthCategoryName = model.Name;
break
;
}
InitCascadeCategory(model, model.ParentId, model.Depth);
}
}
/// <summary>
/// 初始化層級
/// </summary>
/// <param name="query"></param>
/// <param name="parentId"></param>
/// <param name="depth"></param>
private
static
void
InitCascadeCategory(Category query,
int
parentId,
int
depth)
{
if
(depth < 2)
{
return
;
}
foreach
(KeyValuePair<
int
, Category> kv
in
DictCategories)
{
Category model = kv.Value;
if
(parentId == model.Id && model.Depth == depth - 1)
{
switch
(depth)
{
default
:
break
;
case
2:
query.FirstCategoryId = model.Id;
query.FirstCategoryName = model.Name;
break
;
case
3:
query.SecondCategoryId = model.Id;
query.SecondCategoryName = model.Name;
break
;
case
4:
query.ThirdCategoryId = model.Id;
query.ThirdCategoryName = model.Name;
break
;
}
InitCascadeCategory(query, model.ParentId, --depth);
//遞歸
break
;
}
}
}
}
|
而後進行第(3)步,進行緩存。
須要特別說明的是,BaseCategory類咱們只多設計了8個屬性,四個層級(目前爲止開發中超過四個層級的我尚未遇到過),固然你可能會問,若是超過4個層級怎麼辦?曾經看到過有一種通用設計的思路,就是經過一個集合對象(或嵌套的集合對象)進行層級類別的存取,好比泛型Dictionary,LinkedList等等,我尚未嘗試實現過,可是設計實現思路確實能夠借鑑。
(3)、按照某種策略緩存數據,如天天或者每月更新一次數據,等等。
(4)、直接查詢操做緩存中的分類數據。
四、思考
(1)、數據表中Depth字段是否是必要的,是否多餘?
(2)、查詢時如何避免遞歸?
(3)、層級過多(好比超過20層級),有沒有更好的設計和解決方法?
… … … …
越想越感到問題多多,期待您的建議和意見。