本文將專門介紹:我的通用數據訪問層 及 Ajax服務端框架的綜合示例,同時也是為了讓大家能對這二個組件有更多的了解。 因此,本文將以界面截圖以及部分代碼展示的方式來說明,這樣可能會有更感性的認識它。
這個演示網站的特點:
1. 數據訪問全使用了我的通用數據訪問層 + 存儲過程的實現。
2. 頁面使用了大量的Ajax技術,沒有任何的直接提交或回傳(Submit Form or postback),分頁,查詢,對話框,都以Ajax的方式實現。
3. 所有Ajax技術的客戶端全部基于JQuery,服務端則使用我的Ajax服務端框架
4. 客戶端UI使用JQuery Easy-UI
5. 雖然沒有使用Asp.net MVC框架,但卻使用了MVC思想。
運行環境需求:
1. .net framework 3.5
2. Sql Server 2005
| 數據訪問層設計目標 | 調用存儲過程,不管輸入參數多么復雜,不管有多少輸出參數,包含轉換一個結果集到實體列表,只需要一行C#代碼。 |
| Ajax框架的特色 | 服務端中的C#方法或用戶控件對于JS來說是透明的,就好像可以直接訪問一下。 |
| Ajax框架的作用意義 |
接受JS的請求,去調用C#方法或者用戶控件。 至于如何調用方法,如何給方法的參數賦值,最后如何處理返回值給客戶端,就屬于框架本身的事情了。 所有的這一切,對于客戶端來說,更是透明的。這些透明的實現也就是框架的意義了。 |
這個演示網站的業務原型來自于Northwind,但所有的表都是我重新根據示例需要而重新定義的, 數據來源于以前從網頁上獲取的數據,雖然不真實,但更適合于演示。于Northwind不同的是,為了示例, 所有的數據庫操作全使用存儲過程來完成。
第一部分,界面截圖
主菜單

基礎數據包含三個部分,都比較類似,下圖將展示“商品管理”頁面

上圖中,表格最右邊的列全是TextBox,可以直接修改,然后會以Ajax的方式提交到服務器。
點擊每條記錄中的商品名稱,將出現以下對話框,它用于新增或修改單個商品資料

新增訂單界面如下

像很多C/S程序一樣,“客戶”可以從對話框中選擇

像很多C/S程序一樣,“商品”也可以從對話框中選擇

數據全部錄入后的樣子如下圖

點擊按鈕“確定保存此訂單記錄 并繼續新增”,表單將以Ajax方式提交給服務器。
訂單管理
頁面打開后,只有“查詢條件”顯示在頁面上

點擊“查找訂單”按鈕,頁面將以Ajax的方式向服務器發出請求,并將顯示結果

點擊每行中的“訂單編號”,將會出現下圖
示例程序還直接有“查看源代碼”的功能哦
調用數據訪問層的代碼
/// <summary> /// 操作“商品記錄”的業務邏輯層 /// 為了簡單演示,每個方法將打開一個連接。 /// </summary> public sealed class ProductBLL { public int Insert(Product product) { return FishBLLHelper.CallSpExecuteNonQuery("InsertProduct", product); } public int Delete(int productId) { return FishBLLHelper.CallSpExecuteNonQuery("DeleteProduct", null, productId); } public int Update(Product product) { return FishBLLHelper.CallSpExecuteNonQuery("UpdateProduct", product); } /// <summary> /// 獲取指定的商品詳細資料(加載所有字段) /// </summary> /// <param name="productId"></param> /// <returns></returns> public Product GetProductById(int productId) { return FishBLLHelper.CallSpGetDataItem<Product>("GetProductById", null, productId); } // 根據指定的分類ID,以及分頁參數,獲取商品記錄列表 public List<Product> GetProductByCategoryId(int categoryId, PagingInfo pagingInfo) { if( categoryId <= 0 ) { pagingInfo.RecCount = 0; return new List<Product>(0); } return FishBLLHelper.CallSpGetDataItemListPaged<Product>("GetProductByCategoryId", pagingInfo, categoryId); } // 搜索商品 public List<Product> SearchProduct(int categoryId, string searchWord, PagingInfo pagingInfo) { return FishBLLHelper.CallSpGetDataItemListPaged<Product>("SearchProduct", pagingInfo, categoryId, searchWord); } /// <summary> /// 更新指定的商品數量 /// </summary> /// <param name="productId"></param> /// <param name="quantity"></param> /// <returns></returns> public int ChangeProductQuantity(int productId, int quantity) { return FishBLLHelper.CallSpExecuteNonQuery("ChangeProductQuantity", null, productId, quantity); } }
/// <summary> /// 操作“訂單記錄”的業務邏輯層 /// 為了簡單演示,每個方法將打開一個連接。 /// </summary> public sealed class OrderBLL : FishBaseBLL { /// <summary> /// 新增訂單記錄(包含訂單明細) /// </summary> /// <param name="order"></param> /// <returns></returns> public int AddOrder(OrderItem order) { // 以事務的方式創建一個FishDbContext對象,將使用默認的連接字符串 using( FishDbContext db = new FishDbContext(true) ) { // 添加記錄到表Orders,同時獲取新產生ID FishBLLHelper.CallSpExecuteNonQuery(db, "InsertOrder", order); // 為訂單明細設置OrderId,并添加到表[Order Details] order.Detail.ForEach(x => { x.OrderID = order.OrderID; FishBLLHelper.CallSpExecuteNonQuery(db, "InsertOrderDetail", x); }); // 刷新訂單總金額。 FishBLLHelper.CallSpExecuteNonQuery(db, "RefreshOrderSumMoney", null, order.OrderID); // 提交事務。 db.CommitTransaction(); return order.OrderID; } } // 根據指定的查詢日期范圍及分頁參數,獲取訂單記錄列表 public List<OrderItem> Search(object dateRange, PagingInfo pagingInfo) { // 按分頁的方式查詢數據庫。關于分頁的查詢方式請參見:CustomerBLL.GetList() return FishBLLHelper.CallSpGetDataItemListPaged2<OrderItem>("SearchOrder", pagingInfo, dateRange); } /// <summary> /// 根據訂單ID獲取訂單相關的所有信息 /// </summary> /// <param name="orderId"></param> /// <returns></returns> public OrderItem GetOrderById(int orderId) { // 以非事務的方式創建一個FishDbContext對象,將使用默認的連接字符串 // 如果一次只調用一個存儲過程,則不需要創建FishDbContext對象,在調用FishBLLHelper的方法時,會自動創建(使用默認的連接字符串) // 這里“顯式”的創建FishDbContext對象是為了讓二個存儲過程在一個連接內完成調用。 using( FishDbContext db = new FishDbContext(false) ) { OrderItem order = FishBLLHelper.CallSpGetDataItem<OrderItem>(db, "GetOrderById", null, orderId); if( order != null ) order.Detail = FishBLLHelper.CallSpGetDataItemList<OrderDetail>(db, "GetOrderDetails", null, orderId); return order; } } /// <summary> /// 修改指定的訂單狀態 /// </summary> /// <param name="orderId"></param> /// <param name="finished"></param> /// <returns></returns> public int SetOrderStatus(int orderId, bool finished) { return FishBLLHelper.CallSpExecuteNonQuery("SetOrderStatus", null, orderId, finished); } }
Ajax演示代碼
新增訂單,JS 代碼
function btnSubmit_click() { if( $("#hfProductIdList").val().length == 0 ){ $.messager.alert(g_MsgBoxTitle, "沒有明細項目不能保存。", "warning"); return false; } // 獲取訂單明細項目,組合成一個字符串,格式:id=quantity; var detail = ''; $("#tblOrderDetail input[name^=quantity_]").each(function(){ detail += $(this).attr("name").substring(9) + "=" + $(this).val() + ";"; }); $("#hfOrderDetail").val(detail); // 向服務器提交表單。注意URL參數。 var j_dialog = ShowWaitMessageDialog(); $("form").ajaxSubmit({ url: "/AjaxOrder.AddOrder.cs", complete: function() { HideWaitMessageDialog(j_dialog); }, success: function(responseText, statusText) { if (responseText == "1") $.messager.alert(g_MsgBoxTitle, "操作成功", "info", function(){ window.location = window.location; }); else $.messager.alert(g_MsgBoxTitle, "提交失敗,錯誤消息或代碼:<br />" + responseText ,'error'); } }); return false; }
新增訂單,C# 代碼
/// <summary> /// Ajax服務類,提供“訂單記錄”相關操作 /// </summary> public static class AjaxOrder { //-------------------------------------------------------------- // 注意: // 用于Ajax的類,可以是靜態的,方法也可以是靜態的。 //-------------------------------------------------------------- /// <summary> /// 新增訂單 /// </summary> /// <param name="form"></param> /// <returns></returns> public static int AddOrder(OrderSubmitForm form) { OrderItem order = form.ConvertToOrderItem(); int newOrderId = BllFactory.GetOrderBLL().AddOrder(order); return (newOrderId > 0 ? 1 : 0); } } public sealed class OrderSubmitForm : MyDataItem { public DateTime OrderDate; public int CustomerID; public string OrderDetail; public string Comment; }
顯示訂單明細對話框,JS 代碼
function ShowOrderDialog(){ var dom = this; var orderId = $(this).attr("OrderNo"); var url = "/AjaxOrder.Show.cs?id=" + orderId; ShowViewerDialog("divOrderInfo", url, function(){ $("#tblOrderDetail").SetGridStyle().find("a.easyui-linkbutton").linkbutton().click(ShowProductDialog); $("#btnSetOrderStatus").linkbutton().click(function(){ SubmitSetOrderStatus( orderId, dom ); return false; }); }, 800, 530); return false; }
顯示訂單明細對話框,C# 代碼
public static class AjaxOrder { public static string Show(int id) { if( id <= 0 ) throw new MyMessageException("沒有指定OrderId"); OrderItem item = BllFactory.GetOrderBLL().GetOrderById(id); if( item == null ) throw new MyMessageException("指定的ID值無效。不能找到對應的記錄。"); // 執行用戶控件,并返回生成的HTML代碼,用戶控件呈現所需要的數據通過第二個參數傳遞。 return FishWebLib.Ajax.UcExecutor.Execute("~/Controls/OrderInfo.ascx", item); } } public sealed class OrderItem : MyDataItem { public int OrderID { get; set; } public int? CustomerID { get; set; } public DateTime OrderDate { get; set; } public decimal SumMoney { get; set; } public string Comment { get; set; } public bool Finished { get; set; } public string CustomerName { get; set; } public List<OrderDetail> Detail; }
OrderInfo.ascx代碼如下(并沒有與之對應的cs代碼哦,關于這個,以后再談)
<%@ Control Language="C#" Inherits="FishWebLib.Mvc.MyUserControlView<OrderItem>" %> <table cellpadding="4" cellspacing="0" style="width: 99%"> <tr><td style="width: 60px">訂單日期</td><td> <input type="text" class="myTextbox" readonly="readonly" style="width: 200px" value="<%= Model.OrderDate.ToString("yyyy-MM-dd HH:mm:ss") %>" /> </td></tr> <tr><td>客戶</td><td> <input type="text" class="myTextbox" readonly="readonly" style="width: 400px" value="<%= Model.CustomerName.HtmlEncode() %>" /> </td></tr> <tr><td class="vertical">訂單明細</td><td> <table cellpadding="4" cellspacing="0" id="tblOrderDetail" class="GridView"> <tr> <td style="width: 450px">商品名稱</td> <td style="width: 60px">單位</td> <td style="width: 60px">數量</td> <td style="width: 60px">單價</td> </tr> <% foreach( var item in Model.Detail ) { %> <tr> <td><a href="#" ProductId="<%= item.ProductID %>" class="easyui-linkbutton" plain="true"> <%= item.ProductName.HtmlEncode() %></a></td> <td><%= item.Unit.HtmlEncode()%></td> <td><%= item.Quantity %></td> <td><%= item.UnitPrice.ToString("F2") %></td> </tr> <% } %> <tr><td colspan="4" style="text-align: right; padding-right: 10px;"> 訂單總金額:<b><%= Model.SumMoney.ToText() %></b> </td></tr> </table> </td></tr> <tr><td class="vertical">備注</td><td> <textarea class="myTextbox" style="width: 660px; height: 70px" readonly="readonly" > <%= Model.Comment.HtmlEncode()%></textarea> </td></tr> <tr><td></td><td> <label><%= Model.Finished.ToCheckBox("chkFinished", null, false) %>訂單已處理</label> <a id="btnSetOrderStatus" href="#" class="easyui-linkbutton" iconCls="icon-ok">修改訂單狀態</a> </td></tr> </table>
組件性能測試
我在寫這二個組件時,就很關心性能問題,因此特意寫了二個性能的測試項目。
本演示程序的壓縮包解開后,其中有二個目錄(TestAjaxPerformance 和 TestDALPerformance)分別是測試數據訪問層和Ajax框架性能的。
TestDALPerformance是用于測試數據訪問層的項目,對比的對象是“手工代碼方式”和Linq to SQL, EF框架

TestAjaxPerformance是用于測試Ajax框架性能的項目,對比的對象是Asp.net MVC 2

我自己在我的筆記本上測試過,比微軟的同類框架要快。不信您可以自己去運行起來看一下。
用戶手冊,API文檔
為了簡單,貼圖算了。


Fish Li (李奇峰)
浙公網安備 33010602011771號