C#無限極分類樹-創建-排序-讀取 用Asp.Net Core+EF實現之方法二:加入緩存機制,
在上一篇文章中我用遞歸方法實現了管理菜單,在上一節我也提到要考慮用緩存,也算是學習一下.Net Core的緩存機制。
關於.Net Core的緩存,官方有三種實現:
1.In Memory Caching 我理解是在內容中實現,這種方法適用於單服務器的生產環境。
2.a Distributed Cache 分部式緩存實現。
3.Response Cache 這種方式我理解為客戶端緩存。
今天我只用了第一種實現方法,內存中緩存,之所以用這種方法我是覺得我這裡用緩存的初衷是為了減少訪問數據庫的次數,而把訪問數據庫頻率最高的數據轉變為對象而放在緩存裡。對於分部式說實話目前我也只是聽說和了解原理,而沒有做過實現,這裡先不談它。
微軟官方提供的實現In Memory Caching 方法時有一個微軟標准的示例,GitHub地址是 在這裡
示例是用中間件實現的,經過一番學習,用示例裡面提到的方法通過中間件實現了緩存,但怎麼用到Controller裡成了一個難題,也可能是我對中間件的了解不夠深入吧!反正我是不知道該怎麼辦了!無耐之下,還是仔細研究了一下代碼發現在中間件裡面對緩存的調用(包括設置和取出)其實只在下面這段代碼裡實現:

![]()
1 if(!_memoryCache.TryGetValue(cacheKey, out greeting))
2 {
3 // fetch the value from the source
4 greeting = _greetingService.Greet("world");
5
6 // store in the cache
7 _memoryCache.Set(cacheKey, greeting,
8 new MemoryCacheEntryOptions()
9 .SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));
10 _logger.LogInformation($"{cacheKey} updated from source.");
11 }
12 else
13 {
14 _logger.LogInformation($"{cacheKey} retrieved from cache.");
15 }
View Code
也就是 _memoryCache.TryGetValue(cacheKey, out greeting)這一段負責從緩存中取出緩存內容;而_memoryCache.Set(cacheKey, greeting, new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));這個方法調用是把數據放到緩存裡面的!
知道了這些以後就著手改造我的程序了:
1.創建IAdminTreeService.cs接口類:
public interface IAdminTreeService
{
/// <summary>
/// 全部節點數據樹
/// </summary>
AdminUserTree GetAllTreeData { get; }
}
2.創建服務AdminTreeServices.cs,用於實現從數據庫中取出管理菜單數據,用服務方式實現這種方式還是借鑒了官方示例的實現方式,這種實現是有其道理的。道理自己琢磨吧,最起碼有得用接口統一。不再啰嗦直接代碼:

![]()
1 public class AdminTreeServices:IAdminTreeService
2 {
3 /// <summary>
4 /// EF數據訪問配置
5 /// </summary>
6 private readonly ApplicationDbContext _basecontext;
7
8 public AdminTreeServices(ApplicationDbContext context)
9 {
10 _basecontext = context;
11 }
12
13 /// <summary>
14 /// 建立無限極節點樹-管理菜單
15 /// </summary>
16 /// <returns></returns>
17 public AdminUserTree GetAllTreeData
18 {
19 get
20 {
21 AdminUserTree result = new AdminUserTree();
22 //初始化一個節點做為根節點
23 result.NodeID = 0;
24 result.Text = "管理員菜單";
25 result.Url = "";
26 result.ParentID = -1;
27 result.Location = "";
28 result.OrderID = 0;
29 result.Comment = "來源為數據庫";
30 result.ImageUrl = "";
31 result.PermissionID = 0;
32 result.Level = 0;
33 result.ChildNumberl = 0;
34 //把根節點傳遞給遞歸方法去創建子節點
35 result.ChildNode = BuildMenuTree(result, -1);
36 return result;
37
38 }
39
40 }
41
42
43 /// <summary>
44 /// 遞歸創建子節點方法
45 /// </summary>
46 /// <param name="node">要為其分配子節點的父級節點</param>
47 /// <param name="levelID">層級關系</param>
48 /// <returns></returns>
49 protected List<AdminUserTree> BuildMenuTree(AdminUserTree node, int levelID)
50 {
51 var listtree = _basecontext.Admintree;
52
53 //從數據庫中取出node節點的全部子節點 條件:m.ParentID==node.NodeID
54 List<AdminUserTree> lt = listtree.Where(m => m.ParentID == node.NodeID)
55 .Select(m => new AdminUserTree()
56 {
57 NodeID = m.NodeID
58 ,
59 Text = m.Text
60 ,
61 Url = m.Url
62 ,
63 ParentID = m.ParentID
64 ,
65 Location = m.Location
66 ,
67 OrderID = m.OrderID
68 ,
69 Comment = m.Comment
70 ,
71 ImageUrl = m.ImageUrl
72 ,
73 PermissionID = m.PermissionID
74 })
75 .ToList();
76
77 if (lt != null)
78 {
79 //節點深度
80 node.Level = levelID + 1;
81 //子節點數量,便於前端遞歸輸出時調用
82 node.ChildNumberl = lt.Count;
83 for (int i = 0; i < lt.Count; i++)
84 {
85 //遞歸調用創建子節點
86 lt[i].ChildNode = BuildMenuTree(lt[i], node.Level);
87 }
88 return lt;
89
90 }
91 else
92 {
93 return null;
94 }
95
96
97
98
99 }
100
101 }
AdminTreeServices
3.管理頁面基類 AdminBase.cs:

![]()
1 public class AdminBase: Controller
2 {
3 /// <summary>
4 /// EF數據訪問配置
5 /// </summary>
6 private readonly ApplicationDbContext _basecontext;
7 private readonly IAdminTreeService _adminTreeService;
8 private readonly ILogger<AdminUserTree> _logger;
9 private readonly IMemoryCache _memoryCache;
10
11
12 /// <summary>
13 /// 管理菜單 這裡是基數,聲明為屬性以便控制器裡面可以用到
14 /// </summary>
15 public AdminUserTree leftMenu { get; set; }
16
17
18 public AdminBase(ApplicationDbContext context,
19 IMemoryCache memoryCache,
20 ILogger<AdminUserTree> logger,
21 IAdminTreeService adminTreeService)
22 {
23 _basecontext = context;
24 //初始化無限極分類管理菜單
25 _logger = logger;
26 _adminTreeService = adminTreeService;
27 _memoryCache = memoryCache;
28 leftMenu = GetAdminTreeByCache();
29 }
30
31 /// <summary>
32 /// 從緩存中讀取節點樹
33 /// </summary>
34 /// <param name="httpContext"></param>
35 /// <returns></returns>
36 public AdminUserTree GetAdminTreeByCache()
37 {
38 string cacheKey = "AdminTree-Base";
39 AdminUserTree adminTree;
40
41 //獲取緩存內容的一種方式
42 // greeting = _memoryCache.Get(cacheKey) as string;
43
44 //另一種獲取緩存內容的方式,
45 // alternately, TryGet returns true if the cache entry was found
46 if (!_memoryCache.TryGetValue(cacheKey, out adminTree))
47 {
48 //緩存中不存在該內容,從數據接口中通過訪問數據庫獲取。
49 adminTree = _adminTreeService.GetAllTreeData;
50
51 // 絕對過期時間方法,在設置緩存時3分鐘後過期
52 //_memoryCache.Set(cacheKey, adminTree,
53 // new MemoryCacheEntryOptions()
54 // .SetAbsoluteExpiration(TimeSpan.FromMinutes(3)));
55 //相對過期時間方法,相對於最後一次調用後3分鐘過期
56 _memoryCache.Set(cacheKey, adminTree,
57 new MemoryCacheEntryOptions()
58 .SetSlidingExpiration(TimeSpan.FromMinutes(3)));
59 //記錄日志
60 _logger.LogInformation($"{cacheKey} From Data."+DateTime.Now.AddMinutes(3).ToString());
61 }
62 else
63 {
64 //記錄日志,這裡表示當前數據是從緩存中讀取的。
65 _logger.LogInformation($"{cacheKey} From Cacha."+DateTime.Now.ToString());
66 }
67
68 return adminTree;
69
70 }
71 }
AdminBase
4.最後不要忘記了在Startup.cs中加入以下代碼 :
1 public void ConfigureServices(IServiceCollection services)
2 {
3 …………
4 services.AddMemoryCache();
5
6 services.AddTransient<IAdminTreeService, AdminTreeServices>();
7 }
在這裡基本就實現了通過緩存讀取數據了!至於Controller中的實現沒有變化請參考我的上篇隨筆:
C#無限極分類樹-創建-排序-讀取 用Asp.Net Core+EF實現
為了方便閱讀這裡再放一遍菜單節點類代碼:

![]()
1 /// <summary>
2 /// 無限極節點類
3 /// </summary>
4 public class AdminUserTree
5 {
6 /// <summary>
7 /// 節點信息
8 /// </summary>
9 public int NodeID { get; set; }
10 /// <summary>
11 /// 節點名稱
12 /// </summary>
13 public string Text { get; set; }
14 /// <summary>
15 /// 父節點ID
16 /// </summary>
17 public int ParentID { get; set; }
18 /// <summary>
19 /// 對應的鏈接地址
20 /// </summary>
21 public string Url { get; set; }
22 public int? PermissionID { get; set; }
23 public int? OrderID { get; set; }
24 public string Location { get; set; }
25 public string Comment { get; set; }
26 public string ImageUrl { get; set; }
27 /// <summary>
28 /// 層級
29 /// </summary>
30 public int Level { get; set; }
31 /// <summary>
32 /// 子節點數目(很重要)
33 /// </summary>
34 public int ChildNumberl { get; set; }
35
36 /// <summary>
37 /// 子節點 (子節點是一個List)這種用法叫什麼?
38 /// </summary>
39 public List<AdminUserTree> ChildNode { get; set; }
40 }
View Code
最後把運行時日志截圖放出來:
首次訪問:

上面有很多的數據庫訪問語句,日志提示數據來源與數據庫
再次訪問:

這裡就是直接從緩存中讀取出來的內容了!
好了今天的內容就寫到這裡。
按照先前的計劃,下一步該研究一下用戶及權限的內容了,這次我打算研究一下 ASP.NET Identity 身份驗證和基於角色的授權,網上有很多相關的資料了,可我還是沒有看明白怎麼和我實際的項目相結合。估計要花幾天時間了!