ClownFish:比手寫代碼還快的通用數據訪問層


最近花了二個月的業余時間重寫了我以前的通用數據訪問層, 由於是重寫,所以我給這個項目取了個新名字: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 使用說明

點擊此處下載示例代碼(ClownFishDEMO)

點擊此處下載示例代碼(MyMVC)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM