(原創,開源)高靈活度,高適用性,高性能,輕量級的 ORM 實現

GitHub:

https://github.com/iccb1013/sheng.ADO.NET.Plus

 

早前分享過,當時沒有把代碼上傳到Github,只是通過郵件的形式分享給了部分需要的朋友,最近終於有時間簡單整理一下直接上傳到 Github。

目前上傳的最新版本有一些新功能特性,還有一些細節調整有興趣的自己看一下代碼。

代碼的核心實現簡單粗暴,我奉行夠用就好,解決問題就好的思路,不會在最初的版本中就考慮上千萬上億數據balabala之類的問題,但是如果我在工作中遇到了這樣的場景,我會去升級它並解決這樣的問題。

這個組件是我前兩年寫的,可能和現在流行的 dapper 有一些類似,當時我並不知道有 dapper,如果知道的話可能我就直接使用 dapper了。我寫  sheng.ADO.NET.Plus 並不是閒的無聊要造個輪子玩,而是我在自己的項目開發中,切實遇到了一些問題需要解決:使用EF帶來的不便和直接使用ADO.NET帶來的不便,我需要一個介於兩者之間的,高度自由的組件。

=====

 

目前我們所接觸到的許多項目開發,大多數都應用了 ORM 技術來實現與數據庫的交互,ORM 雖然有諸多好處,但是在實際工作中,特別是在大型項目開發中,容易發現 ORM 存在一些缺點,在複雜場景下,反而容易大大增加開發的複雜度及犧牲靈活度。使用 ORM 不寫 SQL 而使數據庫交互變得簡單易行,是否能夠達到預期效果,要畫一個問號。

主要問題可能存在於以下幾點:

1.大幅度犧牲性能,這裏的性能問題不是指什麼單表寫入100萬次的性能對比,而是指基於如EF這樣的框架開發的項目的整體開發模式和特點造成的性能低下。

2.雖然隱藏了數據層面的設計,但並沒有從根本上降低數據訪問複雜度,只是將複雜緯度從一個點(SQL,存儲過程)轉移到另一個點(代碼),以EF爲例,最終生成的代碼性能與C#書寫有很大關係,且難以通過成熟的數據庫技術反查性能瓶頸。

3.對於複雜查詢,ORM 力不從心,雖然從技術角度說實現肯定都能實現,但是代價是不值的。

 

有朋友認爲 ORM 可以使不懂數據庫的開發人員也能在開發中輕鬆實現與數據庫的交互,但是,在大型項目中,讓不懂數據庫的開發人員做這塊工作,Are you kidding me?

 

在我自己的項目開發經驗中,ORM 還存在以下問題:

1.對於大型項目的開發,表示數據的實體類和數據庫層面的持久化設計並非一一對應的關係,使用ORM根據數據庫表生成一一對應的實體類模型,並不能完全適用,這是促使我實現自己的增強組件的重要原因之一;

2.在實體類中,需要進行其它編碼工作,如額外的屬性定義,附加額外的Attribute,部分功能實現和業務操作等,而使用ORM來生成實體類,生成時會覆蓋現有實體類而導致項目自身的編碼工作丟失;

 

直接使用 ADO.NET 又在很多時候過於繁瑣,特別是取值賦值的過程非常冗餘又麻煩,那能不能設計一種機制,既能擁有 ORM 所帶來的一些便利,又不失 ADO.NET 的高性能和自由度呢?

基於這樣的目標,我設計實現了升訊威ADO.NET增強組件。

 

升訊威ADO.NET增強組件  sheng.ADO.NET.Plus 有以下幾個特點:

1.支持所有數據庫原生操作(基於微軟企業庫的數據模塊,並集成了日誌模塊,所有數據庫操作異常使用企業庫寫日誌)

2.解除與數據庫表模型一一對應的關係,由開發人員靈活指定映射關係

3.支持直接使用SQL語句並根據查詢結果在內存中動態映射

4.支持調用存儲過程並根據查詢結果動態映射

5.支持自動化的事務處理,可自動回滾

6.支持一對多的映射關係,即一個實體類可以映射到多張表

7.支持自動填充/補全數據實體類中的數據

8.支持DataSet、DataTable、DataRow多種粒種的內存動態映射。

9.支持簡單SQL構造器,支持自動生成簡單的無模型映射的SQL語句。

10.支持對實體字段的精細化處理,如將實體對象的任意 Property 標記 JsonAttribute 後,將自動以 Json 格式寫入字段。

10.高性能,高靈活性,高可維護性。

  

下面直接從代碼示例中看使用效果:

 

現在假定有 User 表,包括四個字段:Id,Name,Age,ExtraInfo。

我們定義一個簡單的 User 類。(亦可使用其它工具自動生成)。

 

public class User
    {
        public Guid Id
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }

        public string ExtraInfo
        {
            get;
            set;
        }
    }

 

初始化升訊威ADO.NET增強組件核心類 DatabaseWrapper,

private DatabaseWrapper _dataBase = DatabaseUnity.Database;

 

一、簡單數據操作

 

1.插入一條數據:

public void AddUser(User user)
        {
            _dataBase.Insert(user);
        }

升訊威ADO.NET增強組的 Insert 方法原型是:

public bool Insert(object obj)

Insert 方法會自動解析傳入的對象實例,分析對象的類型名稱(User)及其所包括的屬性(Property),自動實現對User表及各字段的動態映射,將數據插入到表中。

 

2.查詢數據

public List<User> GetUserList()
        {
            return _dataBase.Select<User>();
        }

此處原理同上文一樣,Select 方法自動解析對象類型,得到表,字段信息,實現數據的查詢與填充。

 

3.修改數據

public void UpdateUser(User user)
        {
            _dataBase.Update(user);
        }

有些ORM框架,使用跟蹤對象實例的變化的方式,基於特定對象提交數據,但是這種方式的開銷非常大,升訊威ADO.NET增強組沒有采用這種方式,而是直接根據提交的對象實例,更新數據庫表。

 

4.刪除數據

public void RemoveUser(User user)
        {
            _dataBase.Remove(user);
        }

 

需要注意的是,使用上文中的簡單方式進行修改及刪除操作,必須在實體類中指定主鍵字段:

public class User
    {
        [Key]
        public Guid Id
        {
            get;
            set;
        }

       ......
    }

 

至此我們實現了基本的數據庫操作的自動化。

是不是很熟悉,和Entity Framework很類似是不是? 

 

二、自定義實體類與數據庫表的映射關係

上文中的簡單增刪改查操作,是根據對象實例得到對象類型從而得到類型名稱和屬性(Property)集合及他們的名稱,那麼如果實體類型的名稱與數據庫表名稱並不一樣怎麼辦呢?如果數據實體的屬性(Property)與數據庫表字段並不一一對應怎麼辦呢?

在大型項目中,這種情況是經常存在的,對於複雜的數據庫表設計,到了業務層,可能會有不同的解釋方法,例如我有一張用戶表,包含了產品不同維度的信息:基本信息、擴展信息等。到了業務實現層面,我希望展開爲兩個不同的實體對象進行操作,基本信息對象和擴展信息對象。他們所使用的字段可能不太相同,卻又包括了某些共通的字段,如Id,姓名。

如上文所說,升訊威ADO.NET增強組件沒有強制的實體類與數據庫表的映射關係要求,數據庫表中的字段多少與實體類中的屬性多少,或者說表中有的,實體類中沒有,都沒有關係,實體類中有的,通過Attribute標記是否映射即可。

 

1.數據庫表名的映射指定

我們定義兩個不同的實體類:

  [Table("User")]
    public class User_BaseInfo
    {
        [Key]
        public Guid Id
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }
    }

 

[Table("User")]
    public class User_ExtraInfo
    {
        [Key]
        public Guid Id
        {
            get;
            set;
        }

        public string ExtraInfo
        {
            get;
            set;
        }
    }

 

只需在類型定義前加上 TableAttribute ,對 User_BaseInfo 或 User_ExtraInfo 類的對像實例進行操作,直接使用上文中的增刪改查方法即可。至此我們已經開始解除了實體類與數據庫表結果的強關聯。

 

2.數據庫表字段的映射指定

此處嚴格來講,並非一般ORM中針對 數據庫表字段 的映射,而是針對 結果集字段 的映射。比如說通過複雜SQL,存儲過程得到的結果集,根本不是數據庫中的表

在某些場景中,實體類中需要額外定義一些屬性,用於存儲特定信息或實現特定功能,這些數據並不需要進行持久化存儲。或是實體類中的屬性名稱與數據庫表字段名稱存在不完全相同的情況,如將一張表映射到多個數據實體後,爲了區別描述,以及基於複雜查詢(SQL,存儲過程)得到的結果集中的字段名。

 [Table("User")]
    public class User_ExtraInfo
    {
        [Key]
        public Guid Id
        {
            get;
            set;
        }

        [Column("ExtraInfo")]
        public string Infomation
        {
            get;
            set;
        }

        [NotMapped]
        public int Count
        {
            get;
            set;
        }
    }

 

只需在屬性定義前加上 ColumnAttribute 或 NotMapped ,使用上文中的增刪改查方法即可實現相應的操作。

 

3.實體類對數據庫表的多對多映射

此功能用於將二維的數據庫表(或結果集)進一步強類型化

在使用一般ORM框架時,對於複雜的數據庫表結構,常常可以見到非常多的字段定義,但在我們的實際業務中,這些字段可能都有不同的邏輯歸屬,此外,在開發中,我們可能在數據傳遞,操作的過程中,希望只傳遞或公開一部分數據,而不是整個對象進行傳遞

 public class User
    {
        [Key]
        public Guid Id
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }

        [Partial]
        public ExtraInfo ExtraInfo
        {
            get;
            set;
        }
    }

    public class ExtraInfo
    {
        public string ExtraInfo
        {
            get;
            set;
        }
    }

 

只需在對象上加上 PartialAttribute ,表示屬性的對象是 當前數據集 的一部分字段所表示的子對象

PartialAttribute  還提供了 FieldRelationship 用來進一步指定映射關係。

這樣我們實現了實體類對數據表(數據集)的多對一映射,那如何實現多對多的映射呢?實際上非常簡單,使用SQL,視圖,存儲過程進行多表查詢,結合使用 PartialAttribute 即可。

 

三、進階操作

1.高級查詢

除了上文中提到的基本 Select<T>() 方法外,升訊威ADO.NET增強組件提供了額外的幾個進階方式進行數據查詢。

 

a) 基本查詢

public List<T> Select<T>() where T : class,new()

上文已展示。

 

b) 附加查詢條件

public List<T> Select<T>(Dictionary<string,object> attachedWhere) where T : class,new()

通過 attachedWhere 額外的指定查詢條件。Dictionary<string,object> 中的 string 和 object 分別指定字段和字段值。

爲什麼不使用 lamda?在一些場景中不夠靈活。

 

c)通過 SQL 語句進行查詢

既然是ADO.NET增強組件,直接使用SQL來操作當然是重頭戲。

public List<T> Select<T>(string sql) where T : class

直接編寫 SQL 語句進行數據查詢,Select 方法可根據返回的結果集和指定的對象類型進行自動映射,返回強類型對象集合

可以傳遞任意能夠返回結果集的SQL語句,返回的結果集自動與泛型T匹配,泛型T也不一定就是數據庫中的表所映射的對象

 

d) 參數化 SQL 語句查詢

public List<T> Select<T>(string sql, List<CommandParameter> parameterList) where T : class

進行參數化的 SQL 語句查詢,例如:

List<CommandParameter> parameterList = new List<CommandParameter>();
parameterList.Add(new CommandParameter("@extraInfo", "ABC"));
List<User> userList = _dataBase.Select<User>("SELECT * FROM [User] WHERE ExtraInfo = @extraInfo",parameterList);

 

2.與內存中的 DataSet 進行動態映射

當我們使用存儲過程或其它方式得到一個 DataSet 時,升訊威ADO.NET增強組件支持對其進行動態映射,根據 DataSet 數據集得到強類型的對象實例或對象實例的集合。

RelationalMappingUnity 類提供了以下方法:

public static List<T> Select<T>(DataSet ds) where T : class

將 DataSet 視爲一個完整數據源,從中查找指定對象類型所映射的表名進行實例化。

 

public static List<T> Select<T>(DataTable dt) where T : class

使用 DataTable 作爲唯一數據集,對指定的對象類型進行實例化。

 

public static T Select<T>(DataRow dr) where T : class
public static object Select(DataRow dr, Type type)
public static object Select(DataRow dr, Type type, Dictionary<string, string> fieldPair)

上面三個方法提供了更細粒度的操作可能,直接從 DataRow 得到一個強類型的對象實例

 

3.數據填充

很多時候我們需要根據某個已知條件查詢得到對象實例,如我們得到 User 的 Id,希望查詢數據庫表得到 User 對象,在升訊威ADO.NET增強組件中,我們使用 Fill 方法既可。

public bool Fill<T>(object obj) where T : class,new()
public User GetUser(Guid id)
        {
            User user = new User();
            user.Id = id;
            if (_dataBase.Fill<User>(user))
                return user;
            else
                return null;
        }

Fill 方法返回一個 bool 值,表示是否成功查詢並填充了數據。

Fill 方法也有一個高階重載,可以額外指定查詢條件:

public bool Fill<T>(object obj, Dictionary<string, object> attachedWhere) where T : class,new()

 

4.SQL 語句構造器

有時,我們希望直接通過 SQL 語句實現對數據庫表的簡單操作,升訊威ADO.NET增強組件提供了一個 SQL 語句構造器,幫助生成 SQL 語句,可以減輕開發人員編寫 SQL 語句的工作量和出錯的可能性,提高軟件工程的質量。

 public void AddUser(User user)
        {
            SqlStructureBuild sqlStructureBuild = new SqlStructureBuild();
            sqlStructureBuild.Type = SqlExpressionType.Insert;
            sqlStructureBuild.Table = "User";
            sqlStructureBuild.AddParameter("Id", user.Id);
            sqlStructureBuild.AddParameter("Name", user.Name);
            sqlStructureBuild.AddParameter("Age", user.Age);

            SqlExpression sqlExpression = sqlStructureBuild.GetSqlExpression();
            _dataBase.ExcuteSqlExpression(sqlExpression);
        }

ExcuteSqlExpression 方法在執行 SQL 構造器生成的 SqlExpression 對象時,使用的是參數化,強類型的方法進行的。

 

5.事務

對於連續的數據庫操作,升訊威ADO.NET增強組件自動封裝爲一個事務進行執行,如果執行失敗,將自動回滾。

a) 連續寫入操作

非常簡單,直接使用 Insert 方法插入一個對象集合既可,方法原型如下:

public void InsertList(List<object> objList)

連接的寫入操作時,並不要求傳入的參數是同樣類型的,也就是說可以傳入多個不同類似的實體對象,如同時傳入User和Order,升訊威ADO.NET增強組件也會將其封裝爲事務執行,要麼全部寫入成功,要麼回滾。

 

b) 複雜複合操作

對於相對複雜的數據庫事務操作,可使用 SQL 語句構造器,分別構造 SqlExpression 對象,將其按執行順序放入集合中,通過 ExcuteSqlExpression 執行即可。

public void ExcuteSqlExpression(List<SqlExpression> sqlExpressionList)

這種方式執行的多個 SqlExpression 對象,亦封裝爲事務進行執行。

 

四、原生操作

升訊威ADO.NET增強組件支持對數據庫進行原生操作,在此基礎之上,結合上述功能,實現簡單高效高靈活性的數據庫操作。

public int ExecuteNonQuery(string commandText)
public int ExecuteNonQuery(string commandText, List<CommandParameter> parameterList)
public int ExecuteNonQuery(CommandType commandType, string commandText, List<CommandParameter> parameterList)
public object ExecuteScalar(string commandText)
public object ExecuteScalar(string commandText, List<CommandParameter> parameterList)
public object ExecuteScalar(CommandType commandType, string commandText, List<CommandParameter> parameterList)
public DataSet ExecuteDataSet(string commandText)
public DataSet ExecuteDataSet(string commandText, string tableName)
public DataSet ExecuteDataSet(CommandType commandType, string commandText, string tableName)
public DataSet ExecuteDataSet(string commandText, List<CommandParameter> parameterList, string tableName)
public DataSet ExecuteDataSet(CommandType commandType, string commandText,List<CommandParameter> parameterList, string tableName)

 

綜上所述,升訊威ADO.NET增強組件強調的並非實體類與數據庫表結構的強關聯,而是通過與內存數據集的動態映射,將數據庫操作時大量的重複勞動自動化,對於複雜數據庫操作,繼續使用原生 SQL,存儲過程,自定義函數,視圖等。

這種方式結合了 ORM 自動化的優點,又充分利用了數據庫原生操作的強大功能,使數據層的開發輕鬆,高效,高質量。將簡單的,重複的體力勞動,交由程序自動化處理,複雜業務場景由人工處理,並將數據映射,取/賦值等重複勞動,自動化處理。

 

以上設計實現難免存在考慮不周的情況,希望和大家多多交流。

歡迎加我QQ交流探討,共同學習:279060597,另外我在南京,有南京的朋友嗎?

發佈了29 篇原創文章 · 獲贊 21 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章