ORM(Object-Relational Mapping 對象關系映射),它的作用是在關系型數據庫和業務實體對象之間作一個映射,目的是提供易於理解的模型化數據的方法。
ORM雖然有諸多好處,但是在實際工作中,容易發現在大型項目開發中,ORM存在一些缺點,在復雜場景下,反而容易大大增加開發的復雜度及犧牲靈活度。使用ORM不寫SQL而使數據庫交互變得簡單易行,是否能夠達到預期效果,要畫一個問號。
主要問題可能存在於以下幾點:
1.犧牲一定的性能。
2.雖然隱藏了數據層面的設計,但並沒有從根本上降低數據訪問復雜度,只是將復雜緯度從一個點(SQL,存儲過程)轉移到另一個點(代碼)。
3.對於復雜查詢,ORM力不從心,雖然從技術角度說實現肯定都能實現,但是代價是不值的。
此外,最重要的一點,對於大型項目的開發,表示數據的實體類和數據庫層面的持久化設計並非一一對應的關系,使用ORM根據數據庫表生成一一對應的實體類模型,並不能完全適用。
同時,在實體類中,需要進行其它編碼工作,如額外的屬性定義,附加額外的Attribute,部分功能實現和業務操作等,而使用ORM來生成實體類,生成時會覆蓋現有實體類而導致項目自身的編碼工作丟失。
我希望的ORM框架能夠有以下幾個特點(設計目標)
1.支持所有數據庫原生操作(基於微軟企業庫的數據模塊)
2.解除與數據庫表模型一一對應的關系,由開發人員靈活指定映射關系。
3.支持直接使用SQL語句並根據查詢結果動態映射。
4.支持調用存儲過程並根據查詢結果動態映射。
5.支持自動化的事務處理,可自動回滾。
6.支持一對多的映射關系,即一個實體類可以映射到多張表。
7.支持自動填充/補全數據實體類中的數據。
8.高性能,高靈活性,高可維護性。
一句話總結:我需要的不是一個與數據庫表映射的ORM,而是需要一個與內存數據集動態映射的ORM。
源代碼:http://www.cnblogs.com/sheng_chao/p/4560252.html
這樣看來,我想要做的東西還算不算是 ORM ?姑且叫 S-ORM 吧,方便下文引用。
現在假定有 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; } }
初始化 S-ORM 核心類 DatabaseWrapper,
private DatabaseWrapper _dataBase = DatabaseUnity.Database;
一、簡單數據操作
1.插入一條數據:
public void AddUser(User user) { _dataBase.Insert(user); }
S-ORM 的 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,使用跟蹤對象實例的變化的方式,基於特定對象提交數據,S-ORM 沒有采用這種方式,而是直接根據提交的對象實例,更新數據庫表。
4.刪除數據
public void RemoveUser(User user) { _dataBase.Remove(user); }
需要注意的是,使用上文中的簡單方式進行修改及刪除操作,必須在實體類中指定主鍵字段:
public class User { [Key] public Guid Id { get; set; } ...... }
至此我們實現了基本的數據庫操作的自動化。
二、自定義實體類與數據庫表的映射關系
上文中的簡單增刪改查操作,是根據對象實例得到對象類型從而得到類型名稱和屬性(Property)集合及他們的名稱,那么如果實體類型的名稱與數據庫表名稱並不一樣怎么辦呢?如果數據實體的屬性(Property)與數據庫表字段並不一一對應怎么辦呢?
在大型項目中,這種情況是經常存在的,對於復雜的數據庫表設計,到了業務層,可能會有不同的解釋方法,例如我有一張用戶表,包含了產品不同維度的信息:基本信息、擴展信息等。到了業務實現層面,我希望展開為兩個不同的實體對象進行操作,基本信息對象和擴展信息對象。他們所使用的字段可能不太相同,卻又包括了某些共通的字段,如Id,姓名。
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>() 方法外,S-ORM 提供了額外的幾個進階方式進行數據查詢。
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 分別指定字段和字段值。
c)通過 SQL 語句進行查詢
public List<T> Select<T>(string sql) where T : class
直接編寫 SQL 語句進行數據查詢,Select 方法可根據返回的結果集和指定的對象類型進行自動映射,返回強類型對象集合。
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");
2.與內存中的 DataSet 進行動態映射
當我們使用存儲過程或其它方式得到一個 DataSet 時,S-ORM 支持對其進行動態映射,根據 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 對象,在 S-ORM 中,我們使用 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 語句實現對數據庫表的操作,S-ORM 提供了一個 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.事務
對於連續的數據庫操作,S-ORM 自動封裝為一個事務進行執行,如果執行失敗,將自動回滾。
a) 連續寫入操作
非常簡單,直接使用 Insert 方法插入一個對象集合既可,方法原型如下:
public void InsertList(List<object> objList)
b) 復雜復合操作
對於相對復雜的數據庫事務操作,可使用 SQL 語句構造器,分別構造 SqlExpression 對象,將其按執行順序放入集合中,通過 ExcuteSqlExpression 執行即可。
public void ExcuteSqlExpression(List<SqlExpression> sqlExpressionList)
四、原生操作
S-ORM 支持對數據庫進行原生操作,在此基礎之上,結合上述功能,實現簡單高效高靈活性的數據庫操作。
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)
綜上所述,S-ORM 強調的並非實體類與數據庫表結構的強關聯,而是通過與內存數據集的動態映射,將數據庫操作時大量的重復勞動自動化,對於復雜數據庫操作,繼續使用原生 SQL,存儲過程,自定義函數,視圖等。
這種方式結合了 ORM 自動化的優點,又充分利用了數據庫原生操作的強大功能,使數據層的開發輕松,高效,高質量。將簡單的,重復的體力勞動,交由程序自動化處理,復雜業務場景由人工處理,並將數據映射,取/賦值等重復勞動,自動化處理。
以上設計實現難免存在考慮不周的情況,希望和大家多多交流,共同進步。