最近花了二個月的業余時間重寫了我以前的通用數據訪問層, 由於是重寫,所以我給這個項目取了個新名字:ClownFish
如果需要了解ClownFish的使用方法,請點擊ClownFish 使用說明
ClownFish是什么?
ClownFish 是我編寫的一個通用數據訪問層,設計它的目的是為了:
1. 方便在 .net 項目中執行數據訪問任務。
2. 避免直接使用ADO.NET帶來的一大堆高度類似的繁瑣代碼。
3. 提供出色的性能滿足實際項目需要。
ClownFish 具有以下一些技術特色:
1. 高性能:比手寫代碼還快的執行速度。
2. 簡單:執行查詢、將查詢結果轉成實體列表、獲取輸出參數。 一個調用完成三個步驟。
3. 方便:提供專用的代碼生成器,直接生成調用代碼或者實體類型定義代碼。
4. 通用:可以非常簡單地實現對多種數據庫的支持。
5. 靈活:支持存儲過程,參數化SQL,或者將SQL語句保存在XML配置文件中。
6. 可監控:提供一個Profiler工具,讓您可以隨時了解詳細的數據庫訪問情況。
ClownFish不僅繼承了老版本的通用數據訪問層的全部優點, 而且在高性能,方便性,靈活性,以及代碼可讀性方面有了更出色的設計。
在最新的版本中,ClownFish不僅僅只是一個通用數據訪問層, 還提供了:專用的代碼生成器,XmlCommand管理工具,Profiler工具,它們都會為ClownFish提供更多功能。
ClownFish 是一個可免費的數據訪問組件,您可以把它應用在您的 .net 項目中,讓它簡化您的開發工作。
比手寫代碼還快的執行速度
提高性能是創建ClownFish項目的重要原因之一,這次優化的主要目標是:比手寫代碼還快的執行速度。
為了讓您對ClownFish的性能留有較深刻的印象,下面我將通過一個實際案例來測試它的速度。
定義這個實體類型,我關注的是它所包含的數據成員的數量,而不是那些數據成員的含義。
我認為這個類型的數據成員數量應該還是比較接近多數實際場景的。
下面再來看看如何從數據庫中加載它們:
代碼中,首先定義了一個參數化的SQL查詢語句,然后就是純手工的ADO.NET代碼,循環調用SqlDataReader.Read()方法,再從SqlDataReader中根據字段名稱獲取數據寫入到實體對象的屬性中。
這段代碼夠【手工化】吧? 由於這段代碼沒用使用反射代碼,所以如果想追求性能,我想大家都會這樣寫。
測試代碼應該沒有問題吧?
再來看一下使用ClownFish的等效測試代碼:
[TestMethod("ClownFish,SQLSERVER", 2)] public class Test_ClownFish_ShareConnection : IPerformanceTest { private UiParameters uiParam; private ClownFish.DbContext db; public Test_ClownFish_ShareConnection(UiParameters param) { this.uiParam = param; this.db = new ClownFish.DbContext(false); } public List<OrderInfo> Run() { var parameter = new { TopN = uiParam.PageSize }; return ClownFish.DbHelper.FillList<OrderInfo>( TestHelper.QueryText, parameter, db, ClownFish.CommandKind.SqlTextWithParams); } public void Dispose() { db.Dispose(); } }
比較這二段代碼,不難看出:使用 ClownFish 所需的代碼量要 少很多。
如果我以下面的測試參數執行性能測試:
可以得到下面的測試結果:
圖形反映的結果很直觀:ClownFish 完成測試所需時間比手寫代碼要略快點。
或許有些人認為:快這么一點,意義不大!
但您想過沒有:ClownFish PK的對象是【手工版的專用代碼】啊!
其實這個測試並沒有把 ClownFish 性能很好的體現出來,因為中間有SQLSERVER的執行時間,以及跨進程的調用開銷。 這二個因素所花的成本影響了ClownFish的性能優勢。
下面,我又做了一組測試:直接從DataTable中加載數據。
我之所以選擇這個測試方法,是因為它也是一種常見的使用方案:
我們可以將原始導出到XML文件中,然后使用XML文件做離線數據,這樣可以減少對數據庫的訪問壓力。
老版本的通用數據訪問層也一直支持這個功能,所以這次就選擇了這個測試方法。
說明:實際使用時,我會從XML讀出數據到DataTable,供后面使用(轉成實體類型只是其中的一種使用數據的方式)。
看到這個版本的LoadOrderInfo方法,您會有什么感覺?是不是很無奈?
沒辦法,ADO.NET就是這樣設計的。寫這樣的代碼會讓人心煩(這是我的感受)!
測試代碼應該沒有問題吧?
再來看一下使用ClownFish的等效測試代碼:
[TestMethod("ClownFish,DataTable", 6)] public class Test_ClownFish_LoadDataTable : IPerformanceTest { public Test_ClownFish_LoadDataTable(UiParameters param) { } public void Dispose() { } public List<OrderInfo> Run() { return ClownFish.DbHelper.FillListFromTable<OrderInfo>(TestHelper.GetOrderInfoTable()); } }
使用 ClownFish 的代碼仍然要短很多!
下面繼續使用前面的測試參數來運行測試程序,得到以下測試結果:
Excel圖形直觀反映出 ClownFish 的速度要 比手工代碼 快一倍 還不止。
看完這二個測試,ClownFish 有沒有給您留下二個印象?
1. 代碼量很少。
2. 性能很好。
簡單,一個調用完成你要的全部功能
調用下面這個存儲過程,您需要多少行C#代碼?
create procedure InsertProduct( @ProductName nvarchar(50), @CategoryID int, @Unit nvarchar(10), @UnitPrice money, @Quantity int, @Remark nvarchar(max), @ProductID int output ) as begin insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) values( @ProductName, @CategoryID, @Unit, @UnitPrice, @Quantity, @Remark); set @ProductID = scope_identity(); end
在這里,我不想再想寫那種手工版本的ADO.NET代碼,我懶得寫。
來看一下我從示例代碼中摘選出來的調用代碼吧:
Product product = CreateTestProduct(); // 插入一條新記錄 DbHelper.ExecuteNonQuery("InsertProduct", product); sb.AppendFormat("InsertProduct OK, ProductId is : {0}\r\n", product.ProductID);
看到了吧,其實只有一行代碼(中間那行)。
相應的調用代碼:
// 查詢一個分頁列表 var parameters = new GetProductByCategoryIdParameters { CategoryID = 1, PageIndex = 0, PageSize = 3, TotalRecords = 0 }; List<Product> list = DbHelper.FillList<Product>("GetProductByCategoryId", parameters); sb.AppendFormat("存在 {0} 條符合條件的記錄。條件:CategoryID = {1}\r\n", parameters.TotalRecords, parameters.CategoryID);
還是 一個調用 就能完成全部的數據庫訪問工作。
老版本的通用數據訪問層有這樣一個設計目標:
調用存儲過程,不管輸入參數多么復雜,不管有多少輸出參數,包含轉換結果集到實體列表,只需要一行C#代碼。
這一優點在ClownFish中得到了繼承:簡單,一個調用完成你要的全部功能。
方便,你需要的代碼已經准備好了
看到前面的那段演示代碼,你會不會想:我還要定義一個GetProductByCategoryIdParameters類型啊,太麻煩了!
真是那樣嗎? 請看下面的截圖:
注意:ClownFish並非僅僅支持存儲過程,后面將要介紹的XmlCommand也有同樣好的支持。
在工具中,不僅可以直接查看存儲過程代碼,還能生成調用代碼。
工具生成二個調用代碼,你可以選擇其中的一個(因為我不知道你需要哪個)。
說明:調用GetProductByCategoryId需要定義一個類型是因為它包含了輸出參數,如果沒有輸出參數,工具會生成匿名類型:
接下來的事情我想大家都知道該怎么做:把工具生成的代碼COPY到項目中,修改一下調用參數就好了。
定義數據實體類型不再是費力的體力勞動
對於喜歡使用數據實體類型的人來說,手工定義這些類型是件費力且枯燥的體力勞動。
現在好了,ClownFish 的生成器可以減輕你的工作負擔:
還記得本文一開始的那個OrderInfo類型嗎,它是根據一個查詢語句生成的:
說明:根據查詢生成的類型,工具不知道如何命名,需要你在使用時改名。
ClownFish 不僅僅能生成數據實體類型,還能生成對應的增刪改命令及其對應的命令參數:這里涉及到另一個名詞:XmlCommand。它是什么,我后面再說。
這個截圖是想告訴你:以后不必再為增刪改命令以及那些命令參數煩惱了。
執行圖片中那個菜單操作,然后再到ClownFish的另一個輔助工具(XmlCommandTool)去粘貼就可以了。
通用,可以非常簡單地實現對多種數據庫的支持
ClownFish已經從前輩版本中繼承了對多數據庫種類的支持。
本文的結尾處,我提供了可供下載的完整示例,其中就包含有:SQLSERVER, MySql, ACCESS的示例代碼。 以下是其中一個示例的截圖:
ClownFish 支持多種數據庫的原因在於:
ClownFish 在內部使用了DbConnection, DbCommand, DbTransaction這些抽象類型。
ClownFish 提供的API是不區分數據庫種類的,您在注冊連接時,指定什么類型,就是什么類型。
因此,如果您使用存儲過程,或者XmlCommand的話,完全可以做到:一份C#代碼同時支持多種數據庫的。
靈活,SQL語句放在哪里隨便你
對於數據庫的訪問方式目前有以下5種方案:
1. 有些人喜歡使用存儲過程。
2. 有些人不喜歡存儲過程也不喜歡把SQL語句放在C#代碼中。
3. 有些人會在C#中嵌入參數化的SQL語句。
4. 有些人就是喜歡在C#代碼中拼接SQL語句。
5. 還有些人不寫SQL語句而在使用ORM工具。
當然了,還有些人同時混合使用多種方案。
我不知道您屬於哪一類,如果是最后一類,那么我只能說:ClownFish不適合你。
除此之外,ClownFish完全可以滿足您的需要。
對於前4類方式,ClownFish在內部定義一個枚舉來表示:
/// <summary> /// 表示要執行什么類型的命令 /// </summary> public enum CommandKind { /// <summary> /// 表示要執行一個存儲過程或者是一個XmlCommand /// </summary> SpOrXml, /// <summary> /// 表示要執行一個存儲過程 /// </summary> StoreProcedure, /// <summary> /// 表示要執行一個XmlCommand /// </summary> XmlCommand, /// <summary> /// 表示要執行一條沒有參數的SQL語句 /// </summary> SqlTextNoParams, /// <summary> /// 表示要執行一條包含參數的SQL語句 /// </summary> SqlTextWithParams }
ClownFish提供了一個工具類:DbHelper,可以讓您方便地訪問數據庫, 它的許多數據庫訪問方法可以指定一個CommandKind枚舉表示要執行的命令類型:
public static class DbHelper { public static int ExecuteNonQuery( string nameOrSql, object inputParams); public static int ExecuteNonQuery( string nameOrSql, object inputParams, CommandKind cmdKind); public static int ExecuteNonQuery( string nameOrSql, object inputParams, DbContext dbContext); public static int ExecuteNonQuery( string nameOrSql, object inputParams, DbContext dbContext, CommandKind cmdKind); // 其它的數據庫訪問方法就不一一列出了,可以查閱API文檔。 }
DbHelper的所有數據庫訪問方法都提供與以上類似的4個重載方法。
那些方法中,第一個參數可以傳入一個SQL語句,或者一個存儲過程的名字,或者一個XmlCommand的名稱。
參數 cmdKind 的意義就是解釋第一個參數的含義。
XmlCommand是什么?
前面已經多次提到了XmlCommand,這里來解釋一下XmlCommand是什么。
上一小節中的那些數據訪問方法中,有一類較為特殊:不使用存儲過程,也不把SQL語句混在C#代碼中。
對於這種方案,就需要把SQL語句放在配置文件中。
或許有些人知道:我是喜歡用存儲過程的。我之所以喜歡存儲過程,是因為存儲過程的代碼與項目代碼是獨立的, 我可以單獨修改存儲過程。而且,我還喜歡參數化的查詢,反對使用拼接SQL語句。 我認為我是將數據庫做為我的SQL語句保存容器在使用,存儲過程只是這一種方式而已(因為它有參數管理功能)。
除了存儲過程之外,使用配置文件也能完成同樣功能:1. SQL語句的保存容器,2. 提供參數管理功能。
雖然ClownFish的前輩版本也可以支持這種方案, 但是卻沒有定義一種配置文件的存放格式, 因此,需要自己設計配置文件格式,以及提供自己的簡化包裝方法。 對於這種方案來說,本身是沒有任何技術難度的,只是需要定義一種配置文件的存放格式。 現在,ClownFish已經正式提供這種支持,並提供了一個管理工具。
我把保存在配置文件中的SQL語句稱為 XmlCommand ,一個XML文件中可以保存多個XmlCommand, 每個XmlCommand都有一個名稱以便在運行時區分。下面是一個XmlCommand的代碼片段:
<XmlCommand Name="UpdateProduct"> <Parameters> <Parameter Name="@ProductName" Type="String" Size="50" /> <Parameter Name="@CategoryID" Type="Int32" /> <Parameter Name="@Unit" Type="String" Size="10" /> <Parameter Name="@UnitPrice" Type="Currency" /> <Parameter Name="@Quantity" Type="Int32" /> <Parameter Name="@Remark" Type="String" Size="-1" /> <Parameter Name="@ProductID" Type="Int32" /> </Parameters> <CommandText><![CDATA[ update Products set ProductName = @ProductName, CategoryID = @CategoryID, Unit = @Unit, UnitPrice = @UnitPrice, Quantity = @Quantity, Remark = @Remark where ProductID = @ProductID; ]]></CommandText> </XmlCommand>
看到這段代碼,您會不會想:維護它們是不是很麻煩?
其實不是的,ClownFish提供了一個工具,可以輕松地管理它們:
在這個對話框中,每個XmlCommand的屬性都提供了控件編輯功能,完全不需要手工維護那段XML文本。
雙擊某個XmlCommand節點,還可以查看調用代碼:
工具生成的調用代碼會告訴你那個XmlCommnad有多少個參數,您根本不用記住它們。
還有一點:
XmlCommand的XML文件可以是多個,可以按照不同的業務邏輯來組織它們,例如:
對於一個ASP.NET項目來說,ClownFish 還會監視這個已加載的目錄,如果目錄中的配置文件有修改,會自動重新加載。
可監控,圖形的工具會告訴你每個數據訪問的細節
由於ClownFish是個數據訪問層,因此,它知道每次執行數據庫操作的所有信息。 所以,我提供了一個監控工具,用於查看解詳細的數據庫訪問情況。
上圖反映了在執行MySql,Access的訪問情況。
上圖反映了事務的執行情況,以及某個語句執行失敗的情況。
根據工具窗口,我們可以直觀的查看到每個連接中執行了多少次命令, 它們是否屬於同一個事務,它們的類型是存儲過程,XmlCommand,SQL語句,全都一目了然。 而且,還可以知道每個命令的執行參數是什么:
或許有些人看了這個工具后,會認為它是多余的,他總認為有個【SQL Server Profiler】,其它的東西都是沒有意義的。
我不知道他有沒有想過: 用SQL Server Profiler去監視一個局域網的SQL Server實例,你還能分辨哪些操作是由你引發的嗎??
我的工具則不同,它是配合我的數據訪問層一起工作的,由我的數據訪問層告訴工具當前用戶觸發了什么數據庫的操作。
因此在分析本機程序訪問數據庫時,可以很直觀的知道:
您的某個操作引發了什么樣的數據庫調用,以及調用的各種細節參數。
更何況我的工具不止針對SQL Server有效,理論上,.net支持的,我的數據訪問層應該都支持。
前面也說了,它是配合我的數據訪問層一起工作的,
所以,只要是使用ClownFish,哪怕是訪問Access數據庫,也能有這樣的監控效果。(前面有圖片證明)
關於示例代碼
為了能讓更多的人選擇ClownFish,這次我為大家准備了大量的示例代碼。
1. ClownFishDEMO是一個綜合示例,網站型項目。運行效果圖:
2. MultiAccountDEMO是一個多帳套的演示網站。演示了一套代碼處理多帳套的功能。
3. PerformanceTestApp是前面介紹的性能測試項目,你也可以將其它的數據訪問層加入其中,一起做性能測試。
不僅僅是以上三個項目演示了如何使用ClownFish, 我還為以前的 MyMVC框架 的示例程序增加了數據庫訪問功能(以前只支持XML文件)。
現在,MyMVC框架 的演示程序也能通過ClownFish來訪問SQLSERVER。
可以修改 MyMVC框架 演示網站中的web.config來切換數據訪問方法:
<appSettings> <!--DataSoureceKind 表示不同的數據訪問方式,目前支持三個可選的配置值(注意大小寫): 1. XmlFile ,表示使用XML文件中的數據,此選項為默認值。 2. XmlCommand ,表示使用配置文件中的SQL命令去訪問SQLSERVER 3. StoreProcedure ,表示調用SQLSERVER中的存儲過程去訪問數據庫 你可以修改下面的配置,並結合ClownFishSQLProfiler.exe工具去觀察它們的差別。 --> <add key="DataSoureceKind" value="XmlFile" /> </appSettings>
考慮到有些人在配置SQLSERVER時,會遇到一些問題,MyMVC框架的演示程序仍然默認使用XML文件,而不是SQLSERVER數據庫。
但是,由於ClownFish是一個數據訪問層,它的示例只能訪問數據庫了。
如果不知道如何配置示例, 請參考我的博客:如何在IIS6,7中部署ASP.NET網站
還有一件讓我憂慮的事情:示例中還演示了 MySql的訪問!
我建議:如果你平時不使用MySql,那么就跳過示例中的MySql演示部分吧。
最后建議:在下載示例代碼后,首先打開 Readme.txt 文件看一下,謝謝!
如果需要了解ClownFish的使用方法,請點擊ClownFish 使用說明