原本關於T4模板原想分5個章節詳細解說的,不過因為最近比較忙,也不想將整個系列時間拉得太長,所以就將它們整合在一塊了,可能會有很多細節沒有講到,希望大家自己對着代碼與模板去研究。
本章代碼量會比較大,基本將Web層要使用到的大部分函數都用模板生成了出來,而模板中的函數,很多也是互相關聯調用的。另外在DotNet.Utilities(公共函數項目)中也添加與修改了一些類和函數。
需要特別說明的是,在邏輯層添加了July大神編寫的超強上傳類,具體怎么使用功能怎么強大,在后面調用到時會用一個章節詳細說明。呵呵......
1、先了解解決方案中各個新增文件功能,具體的文件對應說明,請查看《數據字典》中的“目錄與文件說明”
這個是各個表對應的邏輯層類,里面是Web層常用的各種函數。Application文件夾是各種公共邏輯層函數類,Systems文件夾是后端系統管理模塊常用函數類。以后添加新的文件時,可以按功能或業務進行對應分類存放。
LogicBase.cs是邏輯層所有模板生成類的父類,里面有兩個虛函數,用來給模板類調用。在有需要的時候,在自定義類中重寫這兩個函數,就可以給模板中的相應函數自動調用執行。
DelCache()函數是模板類中進行添加、修改、刪除、更新等對數據庫記錄進行變更時會同步調用到,主要用於擁有自定義緩存的類中,重寫該函數后,進行前面的各項操作時自動執行該函數,以達到同步理清自定義緩存的功能。
GetExpression()函數是提供給模板類中的緩存加載函數(GetList())使用的。我們在開發時會創建很多表,有些表全部記錄需要加載到緩存中;有一些表記錄不需要加載到緩存當中(比如日志表);同時也有一些表的記錄會過期,只需要加載指定條件的記錄到緩存使用就可以了,對於后者我們就可以使用GetExpression()函數來進行控制,只要重寫這個函數,系統在運行GetList()函數時就會自動加載我們自定義的條件,從數據庫中篩選出我們想要的記錄到緩存中。
CommonBll.cs是邏輯層的工具類,主要提供給模板生成類調用。具體使用方法請看注釋和相關例子。
2、邏輯層T4模板文件CreateBll.tt(文件在文章后面下載源碼里)
模板運行后將會生成下圖中的這些函數
其中IIS緩存又包含下圖中這些常用函數
原來是想使用Redis來處理緩存的,后來考慮到對於中小型項目來說,很多都沒有獨立的空間,使用的是虛擬機,用Redis也就不是很合適了,所以換成IIS緩存來處理
IIS緩存也由之前的表級別緩存修改為記錄級別了,就是說你對某一條記錄進行添加、刪除、修改、更新等操作時,不用清空整個表緩存,直接對緩存中的記錄進行操作,不過這個功能剛剛改為記錄級別,得Web層代碼開始寫后才能測試看看效果怎么樣
3、主要模板函數功能說明
1)模板函數調用使用單例模式
對於中小型項目來說,訪問量並發量並不是很大,單例模式已經夠用了,如果對某一個表並發量過大時,怕出現問題,也可以直接new出這個類,不使用單例調用就可以了
比如:
單例模式調用
InformationBll.GetInstence().GetList();
非單例模式調用
var information = new InformationBll();
information.GetList();
2)表緩存函數

1 private const string const_CacheKey = "Cache_Information"; 2 private const string const_CacheKey_Date = "Cache_Information_Date"; 3
4 #region 清空緩存
5 /// <summary>清空緩存</summary>
6 private void DelAllCache() 7 { 8 //清除模板緩存
9 CacheHelper.RemoveOneCache(const_CacheKey); 10 CacheHelper.RemoveOneCache(const_CacheKey_Date); 11
12 //清除前台緩存
13 CommonBll.RemoveCache(const_CacheKey); 14 //運行自定義緩存清理程序
15 DelCache(); 16 } 17 #endregion
18
19 #region IIS緩存函數
20
21 #region 從IIS緩存中獲取Information表記錄
22 /// <summary>
23 /// 從IIS緩存中獲取Information表記錄 24 /// </summary>
25 /// <param name="isCache">是否從緩存中讀取</param>
26 public IList<DataAccess.Model.Information> GetList(bool isCache = true) 27 { 28 try
29 { 30 //判斷是否使用緩存
31 if (CommonBll.IsUseCache() && isCache){ 32 //檢查指定緩存是否過期——緩存當天有效,第二天自動清空
33 if (CommonBll.CheckCacheIsExpired(const_CacheKey_Date)){ 34 //刪除緩存
35 DelAllCache(); 36 } 37
38 //從緩存中獲取DataTable
39 var obj = CacheHelper.GetCache(const_CacheKey); 40 //如果緩存為null,則查詢數據庫
41 if (obj == null) 42 { 43 var list = GetList(false); 44
45 //將查詢出來的數據存儲到緩存中
46 CacheHelper.SetCache(const_CacheKey, list); 47 //存儲當前時間
48 CacheHelper.SetCache(const_CacheKey_Date, DateTime.Now); 49
50 return list; 51 } 52 //緩存中存在數據,則直接返回
53 else
54 { 55 return (IList<DataAccess.Model.Information>)obj; 56 } 57 } 58 else
59 { 60 //定義臨時實體集
61 IList<DataAccess.Model.Information> list = null; 62
63 //獲取全表緩存加載條件表達式
64 var exp = GetExpression<Information>(); 65 //如果條件為空,則查詢全表所有記錄
66 if (exp == null) 67 { 68 //從數據庫中獲取所有記錄
69 var all = Information.All(); 70 list = all == null ? null : Transform(all.ToList()); 71 } 72 else
73 { 74 //從數據庫中查詢出指定條件的記錄,並轉換為指定實體集
75 var all = Information.Find(exp); 76 list = all == null ? null : Transform(all); 77 } 78
79 return list; 80 } 81 } 82 catch (Exception e) 83 { 84 //記錄日志
85 CommonBll.WriteLog("從IIS緩存中獲取Information表記錄時出現異常", e); 86 } 87
88 return null; 89 } 90 #endregion
只要調用了GetList()函數,系統就會將全表記錄(重寫GetExpression()函數的,只加載符合條件的記錄)以IList<T>存儲方式緩存到IIS緩存中,供其他相關函數使用,緩存當天有效,第二天訪問時會自動清空重新加載
一般來說,前端與后端在一個項目時,后端操作緩存會直接影響前端的數據。如果前后端分開,做為兩個項目來開發時,進行增、刪、改操作時就必須調用DelAllCache()函數來清除前后端的緩存,以便兩個站點的緩存能及時同步(目前對於使用IIS緩存跨站點記錄級別緩存同步還沒有一個比較好的處理方法,只能直接清空整表緩存來實現),調用DelAllCache()函數時,會執行CommonBll.RemoveCache(const_CacheKey)函數,該函數會通過一個前端訪問接口,發送經過加密處理后的數據,前端接口接收到請求后再清除對應緩存來實現前后端IIS緩存同步功能。
3)實體轉換函數

1 #region 實體轉換
2 /// <summary>
3 /// 將Information記錄實體(SubSonic實體)轉換為普通的實體(DataAccess.Model.Information) 4 /// </summary>
5 /// <param name="model">SubSonic插件生成的實體</param>
6 /// <returns>DataAccess.Model.Information</returns>
7 public DataAccess.Model.Information Transform(Information model) 8 { 9 if (model == null) 10 return null; 11
12 return new DataAccess.Model.Information 13 { 14 Id = model.Id, 15 InformationClass_Root_Id = model.InformationClass_Root_Id, 16 InformationClass_Root_Name = model.InformationClass_Root_Name, 17 InformationClass_Id = model.InformationClass_Id, 18 InformationClass_Name = model.InformationClass_Name, 19 Title = model.Title, 20 RedirectUrl = model.RedirectUrl, 21 Content = model.Content, 22 Upload = model.Upload, 23 FrontCoverImg = model.FrontCoverImg, 24 Note = model.Note, 25 NewsTime = model.NewsTime, 26 Keywords = model.Keywords, 27 SeoTitle = model.SeoTitle, 28 SeoKey = model.SeoKey, 29 SeoDesc = model.SeoDesc, 30 Author = model.Author, 31 FromName = model.FromName, 32 Sort = model.Sort, 33 IsDisplay = model.IsDisplay, 34 IsHot = model.IsHot, 35 IsTop = model.IsTop, 36 IsPage = model.IsPage, 37 IsDel = model.IsDel, 38 CommentCount = model.CommentCount, 39 ViewCount = model.ViewCount, 40 AddYear = model.AddYear, 41 AddMonth = model.AddMonth, 42 AddDay = model.AddDay, 43 AddDate = model.AddDate, 44 Manager_Id = model.Manager_Id, 45 Manager_CName = model.Manager_CName, 46 UpdateDate = model.UpdateDate, 47 }; 48 } 49
50 /// <summary>
51 /// 將Information記錄實體集(SubSonic實體)轉換為普通的實體集(DataAccess.Model.Information) 52 /// </summary>
53 /// <param name="sourceList">SubSonic插件生成的實體集</param>
54 public IList<DataAccess.Model.Information> Transform(IList<Information> sourceList) 55 { 56 //創建List容器
57 var list = new List<DataAccess.Model.Information>(); 58 //將SubSonic插件生成的實體集轉換后存儲到剛創建的List容器中
59 sourceList.ToList().ForEach(r => list.Add(Transform(r))); 60 return list; 61 } 62
63 /// <summary>
64 /// 將Information記錄實體由普通的實體(DataAccess.Model.Information)轉換為SubSonic插件生成的實體 65 /// </summary>
66 /// <param name="model">普通的實體(DataAccess.Model.Information)</param>
67 /// <returns>Information</returns>
68 public Information Transform(DataAccess.Model.Information model) 69 { 70 if (model == null) 71 return null; 72
73 return new Information 74 { 75 Id = model.Id, 76 InformationClass_Root_Id = model.InformationClass_Root_Id, 77 InformationClass_Root_Name = model.InformationClass_Root_Name, 78 InformationClass_Id = model.InformationClass_Id, 79 InformationClass_Name = model.InformationClass_Name, 80 Title = model.Title, 81 RedirectUrl = model.RedirectUrl, 82 Content = model.Content, 83 Upload = model.Upload, 84 FrontCoverImg = model.FrontCoverImg, 85 Note = model.Note, 86 NewsTime = model.NewsTime, 87 Keywords = model.Keywords, 88 SeoTitle = model.SeoTitle, 89 SeoKey = model.SeoKey, 90 SeoDesc = model.SeoDesc, 91 Author = model.Author, 92 FromName = model.FromName, 93 Sort = model.Sort, 94 IsDisplay = model.IsDisplay, 95 IsHot = model.IsHot, 96 IsTop = model.IsTop, 97 IsPage = model.IsPage, 98 IsDel = model.IsDel, 99 CommentCount = model.CommentCount, 100 ViewCount = model.ViewCount, 101 AddYear = model.AddYear, 102 AddMonth = model.AddMonth, 103 AddDay = model.AddDay, 104 AddDate = model.AddDate, 105 Manager_Id = model.Manager_Id, 106 Manager_CName = model.Manager_CName, 107 UpdateDate = model.UpdateDate, 108 }; 109 } 110
111 /// <summary>
112 /// 將Information記錄實體由普通實體集(DataAccess.Model.Information)轉換為SubSonic插件生成的實體集 113 /// </summary>
114 /// <param name="sourceList">普通實體集(DataAccess.Model.Information)</param>
115 public IList<Information> Transform(IList<DataAccess.Model.Information> sourceList) 116 { 117 //創建List容器
118 var list = new List<Information>(); 119 //將普通實體集轉換后存儲到剛創建的List容器中
120 sourceList.ToList().ForEach(r => list.Add(Transform(r))); 121 return list; 122 } 123 #endregion
由於SubSonic3.0插件生成的Model附加了很多功能,在對實體進行賦值操作時使用了Linq運算,判斷該字段是否有進行賦值操作,沒有的話在最終生成SQL時,會自動過濾掉該字段。這個功能非常不錯,但是將實體存儲到緩存中或進行Json轉換等操作時,由於這個運算導致程序進入了死循環,無法運行,所以必須將它進行一次轉換,轉化為最常見的普通實體,具體大家可以查看Solution.DataAccess層SubSonic文件夾下ActiveRecord.tt模板生成的類與CreateModel.tt模板生成的類文件比較一下。實體轉換函數就是將這兩種不同的實體進行相互轉換的函數
SubSonic3.0插件ActiveRecord.tt生成的Model
自定義CreateModel.tt模板生成的實體
4)獲取DataTable與綁定Grid表格

1 #region 獲取Information表記錄
2 /// <summary>
3 /// 獲取Information表記錄 4 /// </summary>
5 /// <param name="norepeat">是否使用去重復</param>
6 /// <param name="top">獲取指定數量記錄</param>
7 /// <param name="columns">獲取指定的列記錄</param>
8 /// <param name="pageIndex">當前分頁頁面索引</param>
9 /// <param name="pageSize">每個頁面記錄數量</param>
10 /// <param name="wheres">查詢條件</param>
11 /// <param name="sorts">排序方式</param>
12 /// <returns>返回DataTable</returns>
13 public DataTable GetDataTable(bool norepeat = false, int top = 0, List<string> columns = null, int pageIndex = 0, int pageSize = 0, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null) { 14 try
15 { 16 //分頁查詢
17 var select = new SelectHelper(); 18 return select.SelectDataTable<Information>(norepeat, top, columns, pageIndex, pageSize, wheres, sorts); 19 } 20 catch (Exception e) 21 { 22 //記錄日志
23 CommonBll.WriteLog("獲取Information表記錄時出現異常", e); 24
25 return null; 26 } 27 } 28 #endregion
29
30 #region 綁定Grid表格
31 /// <summary>
32 /// 綁定Grid表格,並實現分頁 33 /// </summary>
34 /// <param name="grid">表格控件</param>
35 /// <param name="pageIndex">第幾頁</param>
36 /// <param name="pageSize">每頁顯示記錄數量</param>
37 /// <param name="wheres">查詢條件</param>
38 /// <param name="sorts">排序</param>
39 public void BindGrid(FineUI.Grid grid, int pageIndex = 0, int pageSize = 0, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null) { 40 //用於統計執行時長(耗時)
41 var swatch = new Stopwatch(); 42 swatch.Start(); 43
44 try { 45 // 1.設置總項數
46 grid.RecordCount = GetRecordCount(wheres); 47 // 如果不存在記錄,則清空Grid表格
48 if (grid.RecordCount == 0) { 49 grid.Rows.Clear(); 50 return; 51 } 52
53 // 2.查詢並綁定到Grid
54 grid.DataSource = GetDataTable(false, 0, null, pageIndex, pageSize, wheres, sorts); 55 grid.DataBind(); 56
57 } 58 catch (Exception e) { 59 // 記錄日志
60 CommonBll.WriteLog("獲取用戶操作日志表記錄時出現異常", e); 61
62 } 63
64 // 統計結束
65 swatch.Stop(); 66 // 計算查詢數據庫使用時間,並存儲到Session里,以便UI顯示
67 HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString(); 68 } 69 #endregion
70
71 #region 綁定Grid表格
72 /// <summary>
73 /// 綁定Grid表格,使用內存分頁,顯示有層次感 74 /// </summary>
75 /// <param name="grid">表格控件</param>
76 /// <param name="parentValue">父Id值</param>
77 /// <param name="wheres">查詢條件</param>
78 /// <param name="sorts">排序</param>
79 /// <param name="parentId">父Id</param>
80 public void BindGrid(FineUI.Grid grid, int parentValue, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> sorts = null, string parentId = "ParentId") { 81 //用於統計執行時長(耗時)
82 var swatch = new Stopwatch(); 83 swatch.Start(); 84
85 try
86 { 87 // 查詢數據庫
88 var dt = GetDataTable(false, 0, null, 0, 0, wheres, sorts); 89
90 // 對查詢出來的記錄進行層次處理
91 grid.DataSource = DataTableHelper.DataTableTidyUp(dt, "Id", parentId, parentValue); 92 // 查詢並綁定到Grid
93 grid.DataBind(); 94
95 } 96 catch (Exception e) { 97 // 記錄日志
98 CommonBll.WriteLog("綁定表格時出現異常", e); 99
100 } 101
102 //統計結束
103 swatch.Stop(); 104 //計算查詢數據庫使用時間,並存儲到Session里,以便UI顯示
105 HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString(); 106 } 107
108 /// <summary>
109 /// 綁定Grid表格,使用內存分頁,顯示有層次感 110 /// </summary>
111 /// <param name="grid">表格控件</param>
112 /// <param name="parentValue">父Id值</param>
113 /// <param name="sorts">排序</param>
114 /// <param name="parentId">父Id</param>
115 public void BindGrid(FineUI.Grid grid, int parentValue, List<string> sorts = null, string parentId = "ParentId") { 116 BindGrid(grid, parentValue, null, sorts, parentId); 117 } 118 #endregion
主要是用於通過條件查詢,獲取指定表記錄,然后與后端的FineUI.Grid綁定,具體使用例子會在Web層的相關章節中說明
大家在看源碼時會發現CommonBll.WriteLog("獲取Information表記錄時出現異常", e);這樣的代碼,其實在整個框架中這種代碼大量存在,它會將出現的異常或操作步驟,忠實的記錄指定目錄的文本文件中,方便我們查看分析。特別是項目上線后,在生產環境中我們很多時候是不能直接對生產環境進行測試的,而用戶是做了什么操作才出現這種異常的沒有日志記錄就很難進行排查,所以我們在編寫自定義邏輯層函數時,也隨手將這樣的代碼寫上,以方便我們以后分析問題。
而HttpContext.Current.Session["SpendingTime"] = (swatch.ElapsedMilliseconds / 1000.00).ToString();這種代碼記錄的是執行查詢綁定FineUI.Grid花費的時間,並會在Web層相關列表頁面顯示出來,方便我們了解頁面的執行效率。
模版會生成兩種類型的Grid表格綁定函數,一個是正常的列表綁定,一個是用於多級分類時有層次感顯示的列表綁定。正常列表是直接從數據庫中查詢出來,而有層次感的列表,使用的是內存分頁,而不是整表緩存。它是將按條件查詢出來的記錄緩存到內存中,在點擊翻頁時,是在內存中獲取分頁然后顯示。
有層次感列表例子:
5)添加與編輯記錄函數

1 #region 添加與編輯Information表記錄
2 /// <summary>
3 /// 添加與編輯Information記錄 4 /// </summary>
5 /// <param name="page">當前頁面指針</param>
6 /// <param name="model">Information表實體</param>
7 /// <param name="isCache">是否更新緩存</param>
8 public void Save(Page page, Information model, bool isCache = true) 9 { 10 try { 11 //保存
12 model.Save(); 13
14 //判斷是否啟用緩存
15 if (CommonBll.IsUseCache() && isCache) 16 { 17 SetModelForCache(model); 18 } 19
20 //添加用戶訪問記錄
21 UseLogBll.GetInstence().Save(page, "{0}" + (model.Id == 0 ? "添加" : "編輯") + "Information記錄成功,ID為【" + model.Id + "】"); 22 } 23 catch (Exception e) { 24 var result = "執行InformationBll.Save()函數出錯!"; 25
26 //出現異常,保存出錯日志信息
27 CommonBll.WriteLog(result, e); 28 } 29 } 30 #endregion
執行更新操作后,會檢查是否啟用了緩存功能,啟用了由會同步更新緩存記錄。然后執行用戶操作日志記錄功能,將用戶執行了什么操作更新到對應的數據庫中。其他刪除與更新操作都會做同樣的記錄。(有了詳細的操作日志記錄,萬一后端系統管理人員操作出了什么問題,要落實操作責任也就很容易查找出來)
6)保存排序與自動排序函數

1 #region 保存列表排序
2 /// <summary>
3 /// 保存列表排序,如果使用了緩存,保存成功后會清空本表的所有緩存記錄,然后重新加載進緩存 4 /// </summary>
5 /// <param name="page">當前頁面指針</param>
6 /// <param name="grid1">頁面表格</param>
7 /// <param name="tbxSortId">表格中綁定排序的表單名</param>
8 /// <param name="sortIdName">排序字段名</param>
9 /// <returns>更新成功返回true,失敗返回false</returns>
10 public bool UpdateSort(Page page, FineUI.Grid grid1, string tbxSortId, string sortIdName = "SortId") 11 { 12 //更新排序
13 if (CommonBll.UpdateSort(page, grid1, tbxSortId, "Information", sortIdName, "Id")) 14 { 15 //判斷是否啟用緩存
16 if (CommonBll.IsUseCache()) 17 { 18 //刪除所有緩存
19 DelAllCache(); 20 //重新載入緩存
21 GetList(); 22 } 23
24 //添加用戶操作記錄
25 UseLogBll.GetInstence().Save(page, "{0}更新了Information表排序!"); 26
27 return true; 28 } 29
30 return false; 31 } 32 #endregion
33
34 #region 自動排序
35 /// <summary>自動排序,如果使用了緩存,保存成功后會清空本表的所有緩存記錄,然后重新加載進緩存</summary>
36 /// <param name="page">當前頁面指針</param>
37 /// <param name="strWhere">附加Where : " sid=1 "</param>
38 /// <param name="isExistsMoreLv">是否存在多級分類,一級時,請使用false,多級使用true,(一級不包括ParentID字段)</param>
39 /// <param name="pid">父級分類的ParentID</param>
40 /// <param name="fieldName">字段名:"SortId"</param>
41 /// <param name="fieldParentId">字段名:"ParentId"</param>
42 /// <returns>更新成功返回true,失敗返回false</returns>
43 public bool UpdateAutoSort(Page page, string strWhere = "", bool isExistsMoreLv = false, int pid = 0, string fieldName = "SortId", string fieldParentId = "ParentId") 44 { 45 //更新排序
46 if (CommonBll.AutoSort("Id", "Information", strWhere, isExistsMoreLv, pid, fieldName, fieldParentId)) 47 { 48 //判斷是否啟用緩存
49 if (CommonBll.IsUseCache()) 50 { 51 //刪除所有緩存
52 DelAllCache(); 53 //重新載入緩存
54 GetList(); 55 } 56
57 //添加用戶操作記錄
58 UseLogBll.GetInstence().Save(page, "{0}對Information表進行了自動排序操作!"); 59
60 return true; 61 } 62
63 return false; 64 } 65 #endregion
保存排序函數是將列表中直接填寫的排序直接保存到數據庫中
自動排序函數執行后,會將當前頁面所綁定表格的所有記錄分級別全部從小到大重新進行排序,比如二級分類原排序值為1、2、5、10、11、12,執行后會變成1、2、3、4、5、6。
同時會清除緩存,並且添加用戶操作記錄。
Web層會將這兩個函數進行封裝,處理后無需再編寫代碼,只要添加了對應按鍵就會自支繼承相應功能,減少Web層的重復代碼編寫
由於使用了新的工具類庫,模板也做了一些修改,代碼實現就花了好幾天才完成,而直接完成代碼后,文章都不知道怎么寫才合適了,今天完成后反復看了幾遍,都覺得寫得差強人意,呵呵......
除了上面介紹的函數外,還有其他類與模板函數大家自己看吧,有什么問題再發上來大家一起討論
4、小結
寫到這里,其實框架的底層結構算是基本完成的,T4模板與SubSonic3.0的結合,產生一個輕量級的開發框架,無論是開發Winform、Web服務還是其他軟件,在這個組合下都可以令我們輕松應對,去除了大量的重復編碼時間,輕輕松松一鍵生成我們需要的大部分代碼。而模板設計合理的話,應用一些新技術或替換某些功能(比如IIS模板換成Redis模板),Web層基本上不用做什么修改就可以直接使用了。對於數據庫添加新表新字段,修改或刪除字段操作,也變得很輕松。由於整個設計不存在硬編碼,就算有些地方要修改,運行一下編譯就能馬上知道需要修改那個位置。
點擊下載:
版權聲明:
本文由AllEmpty原創並發布於博客園,歡迎轉載,未經本人同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。如有問題,可以通過1654937@qq.com 聯系我,非常感謝。
發表本編內容,只要主為了和大家共同學習共同進步,有興趣的朋友可以加加Q群:327360708 ,大家一起探討。
更多內容,敬請觀注博客:http://www.cnblogs.com/EmptyFS/