對象關係映射
AgileEAS.NETORM並沒有採用如NHibernate中對應檔的檔案的模式,而是採用了直接硬式編碼模式實現,ORM體系設計採用了屬性/列>資料對象>資料集合(表)的結構:
最基本的思路是一個記錄/實體(IEntity)映射一條記錄,一個實體包括若干屬性/列(Colunm),而一組IEntity和一組Colunm組成一個資料表對象(IEntity),用於對象一個表、視圖、或者一個查詢結果,下面我涉及到的類、介面介紹一下:
Column
相當於一個資料庫表的一個列。組織於資料庫與資料庫表行中,由1-n個列組成一行資料或一個資料庫表,包含了標題、名稱、資料類型、資料庫列名、大小、值運算式、是否自動成長、值、預設值等屬性。這些屬性方便使用者在定義資料庫實體類及表類時使用,在資料庫屬性類中,常規情況下,一個屬性等同於一個數庫列,但是,一個屬性並不一定關聯一個資料庫列,也可以關聯到一個函數或常量之上。
IEntity
實體介面相關於關聯式資料庫中的一個資料庫表行,把一個資料庫表行映射庫一個資料庫記錄。
資料庫實體介面和資料表介面是ORM中最核心的一個介面,為什麼說他是最核心的介面呢,他是ORM映射中的資料實體物件(Object)、他和關聯式資料庫中的關係(表)進行直接的映射、一個資料庫表行就是一個只有一個元素的關係(即只有一條記錄的表)、資料表是資料庫表行的一個縱行擴充。 資料庫實體介面中實現了,和資料庫表行映射所需求的屬性集合,同時也提供了Refresh、Insert、Update、Save、Delete資料庫持久化操作和一個CacheRefresh方法。
Refresh方法是資料實體物件從關聯式資料庫表行同步自身的一個方法,他從關聯式資料庫表中取出指定行資料,同步內在中的資料實現對象。
Save方法是資料實體物件根據把自己同步到關聯式資料庫表中的一個方法,當資料庫表中存在這條資料行是,修改資料庫表中的這一行,如果資料庫表行中不存在這一行,則向資料庫表中插入這一行。
Insert方法不進行判讀直接向資料庫插入資料。
Update方法不進行判讀直接更新資料庫記錄。
Delete方法是資料實體物件從關聯式資料庫刪除與資料實體物件映射的那一行資料。
CacheRefresh方法同Refresh只是不從資料庫而是從緩衝。
ITable
資料表相關於關聯式資料庫中的一個資料庫表,把一個資料庫表映射庫一個資料表,ITable中我們定義了Columns列集合、Rows行集合,同時也提供了Query、Save、Delete資料庫持久化操作方法和緩衝查詢CacheQuery。
Query方法是資料表對象從關聯式資料庫表同步自身的一個方法,他從關聯式資料庫表中取出指定行資料,同步記憶體中的資料表對象。
Query方法在執行過程中,可以一次全部同步資料庫表資料,也可以根據條件同步資料庫的某一部分資料,在進行條件參數時,需求使用到查詢條件對象(Condition)、查詢條件單元對象(Element)和結果排序單元對象(OrdeElement)、由這三個對象組合成複雜的查詢條件,通過Query方法查詢指定條件的資料。
Save方法和Delete方法實現和IEntity定義中的有相似的功能,在此先不作介紹。
CacheQuery實現從緩衝同步資料行。
在上面ORM的對象架構中,涉及到兩個集合類EntityCollection、ColumnCollection在此文不做特別說明,詳細請參考開發包中的類庫協助,下面我說一下ORM中的查詢條件。
我們知道在進行資料庫操作中要進行條件查詢,我們把行、列、表都進行了對象映射,那麼SQL條件怎麼辦,AgileEAS.NET中定義了三個類,查詢條件(Condition)、組成條件的元素(Element)、排序條件元素。
Condition
條件類是ORM中的一個功能輔助類,他相當於開發人員在編號SQL語句的過程中所編寫的一組查詢條件。條件由條件單元組件,如果條件用於查詢,在查詢時,需要對查詢結果排序,剛需求使用排序條件單元,以下是條件類及條件單元的結構關係:
為條件、條件單元(Element)、排序單元(OrdeElement)之間的類別關係圖,條件元素()由方法關聯條件單元及排序單元,條件單元(Element)表示的是一個很簡單的條件元素,比如 NAME = ‘james’, 排序單元(OrdeElement)也只是表示一個很簡單的排序單元,比如:NAME或 NAME DESC,但是在我們進行的公司專屬應用程式開發中,條件都是很複雜的,比如有這樣的條件 NAME = ‘james’ And SEX = ‘男’ And Age < 16,這樣的怎麼麼寫,我們使用條件單元組成複雜的條件。這個條件我們使用ORM進行映射:
Condition condition = table.CreateCondition ();
condition.AddElement(new Element(“NAME”,”james”);
condition.AddElement(new Element(“SEX”,”男”);
condition.AddElement(new Element(“Age”,” 16”);
這樣就完成了這個條件的定義,在條件的組合及定義條件的過程中,我們就有一個認識,條件並不是光有等值比較,還包括有很多其他的條件類型,同樣,條件的組合不光是And 還有Or組合,是ORM條件映射中的兩個輔助枚舉,列舉條件類型及條件組合類別型。
ElementType
條件類型枚舉,定義某個條件的類型,比如=,<,like之類的:
ElementType/// <summary>/// Condition 類的資料元素(Element)類型枚舉。/// </summary>public enum ElementType{/// <summary>/// “=”比較指定值與指定屬性/// </summary>EqualTo = 0x00000000,/// <summary>/// “>”比較指定值與指定屬性/// </summary>GreaterThan = 0x00000001,/// <summary>/// “>=”比較指定值與指定屬性/// </summary>GreaterThanAndEqualTo = 0x00000002,/// <summary>/// “(不等於)”"比較指定值與指定屬性/// </summary>NotEqualTo = 0x00000003,/// <summary>/// “(小於)”比較指定值與指定屬性/// </summary>LessThan = 0x00000004,/// <summary>/// “小於等於”"比較指定值與指定屬性/// </summary>LessThanAndEqualTo = 0x00000005,/// <summary>/// (BetWeen),介於兩個介之間。/// </summary>BetWeen=0x00000010,/// <summary>/// (NotBetWeen),不介於兩個介之間。/// </summary>NotBetWeen=0x00000020,/// <summary>/// 指定子字串與指定屬匹配(模式比對%value%)/// </summary>Match = 0x00000100,/// <summary>/// 指定子字串與指定屬匹配(首碼匹配value%)/// </summary>MatchPrefix = 0x00000200,/// <summary>/// 指定子字串與指定屬匹配(尾碼匹配%value)/// </summary>MatchSuffix = 0x00000300,/// <summary>/// 指定子字串與指定屬不匹配(%value%)。/// </summary>NotMatch = 0x00000400,/// <summary>/// 指定子字串與指定屬不匹配(首碼不匹配value%)。/// </summary>NotMatchPrefix = 0x00000500,/// <summary>/// 指定子字串與指定屬不匹配(尾碼不匹配%value)。/// </summary>NotMatchSuffix = 0x00000600,/// <summary>/// 指定子字串中的字匹配(字匹配value__value)。/// </summary>WordMatch = 0x00000700,/// <summary>/// 指定子字串中的字不匹配(字匹配value__value)。/// </summary>NotWordMatch = 0x00000800,/// <summary>/// 與給定的列表list裡的值匹配。/// </summary>In = 0x00001000,/// <summary>/// 與給定的列表list裡的值不匹配。/// </summary>NotIn = 0x00002000,/// <summary>/// 直接構造字串,傳入就是條件元素。/// </summary>SqlCondition = 0x00008000}
在這些定義中,有一個特殊的條件類型,SqlCondition條件類型,我們在進行條件映射時,現實中的資料總是複雜的,有很多無法直接使用各種條件映射出,或者,通過單條件映射組件條件很複雜,我們可以直接使用SQL語句作為條件,在這個時間,就可以使用SqlCondition條件類型。它為我們保留了編寫優質高效SQL語名的介面。
ElementCombineType
兩個條件或者兩條條件元素的組合方式,OR或者AND
/// <summary>/// Condition 類的資料元素(Element)之間的組合方法。/// </summary>public enum ElementCombineType{/// <summary>/// And,兩個Element 對象之間是以And 為組合條件。/// </summary>And = 0x0000,/// <summary>/// Or,兩個Element 對象之間是以Or 為組合條件。/// </summary>Or = 0x0001}
介面驅動的資料層
AgileEAS.NET平台一直在實踐著介面驅動的思想,同時也在建議應用開發基於介面驅動,AgileEAS.NET平台實現一組實用並且簡單的ORM,應用開發的資料訪問層也就是基於ORM技術的資料訪問層。
我們在應用開發中,經常會遇到同樣的產品需要運行在不同的資料庫系統之上,比台有客戶需要運行在SQLServer之上的版本、有的需要運行在ORACLR之上的版本。
在這種情況下,採用介面驅動的資料訪問層是一個不錯的選擇;定義一組資料訪問層介面組件及其不同資料庫類型的的資料訪問層實現組件,業務實現依賴於資料介面層而與資料實現層解耦,運行期不同的資料庫類型需求只需要修改系統的設定檔。
我來列舉一個例子,我做也一個資料訪問的例子,我們定義了一個名稱為EAS.Exam.DAL.Interface的類庫項目,包含了IIteminfo(服務項目)、IIteminfoList(服務項目表)、IProduct(產品)、IProductList(產品表)四個實體介面,一個管理這四個四體類具體執行個體庫的IDALManager介面,由他來完成實體的執行個體化,下面看定義:
public interface IDALManager
{
IIteminfo CreateIteminfo();
IIteminfoList CreateIteminfoList();
IProduct CreateProduct();
IProductList CreateProductList();
}
同時在EAS.Exam.DAL.Interface項目中我們定義了一個DALHelper的類:
public class DALHelper
{
static string ComponentKey = "EAS.Exam.DAL";
public static IDALManager DALManager
{
get
{
return ContextHelper.GetContext().Container.GetComponentInstance (ComponentKey) as IDALManager;
}
}
}
DALHelper類是一個輔助類,提供了一個DALManager名稱的靜態屬性,返回一個具體執行個體化的IDALManager對象,可能是SQLServer的實現也有可能是Oracle的實現。
我們在定義一個項目EAS.Exam.DAL.SQLServer分別實現這四個實體接品和DALManager介面:
class DALManager : IDALManager
{
#region IDALManager 成員
public IIteminfo CreateIteminfo()
{
return new Iteminfo();
}
public IIteminfoList CreateIteminfoList()
{
return new IteminfoList();
}
public IProduct CreateProduct()
{
return new Product();
}
public IProductList CreateProductList()
{
return new ProductList();
}
#endregion
}
Oracle資料庫的實現EAS.Exam.DAL.SQLServer參考SQLServer的實現。
然後在系統設定檔(IOC)配置部分增加一個名稱為EAS.Exam.DAL的對象定義,assembly和type根據需要的資料訪問層實現進行配置。
SQLServer資料訪問層:
<object name="EAS.Exam.DAL" LifestyleType="Singleton"
assembly="EAS.Exam.DAL.SQLServer" type="EAS.Exam.DAL.SQLServer.DALManager" />
Oracle資料訪問層:
<object name="EAS.Exam.DAL" LifestyleType="Singleton"
assembly="EAS.Exam.DAL.Oracle" type="EAS.Exam.DAL.Oracle.DALManager" />
在基於介面驅動的資料訪問層中,上面的例子中使用了IOC容器解耦,我們推薦應用開發使用這種模式,也支援程式員研究採用新的項目,比如抽像工作方法進行解偶。
工具的支援
在應用開發中,可以選擇手工編碼資料層代碼,也可以使用AgileEAS.NET平台提供的資料對象設計器產生ORM及基於介面驅動的分層代碼實現。
在AgileEAS.NET平台中,我們提供了一個集資料庫設計、代碼產生、DDL定義與一體的資料實體設計器:
在早期的AgileEAS.NET版本中包含一個代碼產生器,用於根據現在資料庫產生ORM代碼,後來的思路是想介入項目的資料庫設計環節,所以設計了這麼一個資料對象設計器,提供一個資料表定義工具,在項目的資料庫設計階段(環節),使用資料表設計工具同時定義資料庫和資料實體模型。
當定義好模型之後,可以直接生存資料庫設計文檔以及資料庫定義語言DDL(資料能產生ORACLE和SQLSERVER兩種資料庫)。
以及基於部分類別的ORM代碼和基於介面驅動的DAL解決方案和項目,這些要說明一個問題是部分類別,在應用開發中,我們對資料庫的操作不僅僅是讀取、更新、增加、刪除這樣的簡單操作,在DAL層中還有配合商務邏輯的複雜資料庫處理,這就需要程式員在產生的程式碼上進行修改,這就引發一個問題,當我們修改了資料定義模組之後使用工具重建代碼之後就會覆蓋原來的操作,為解決這個矛盾,AgileEAS.NET引入部分類別的技術,將一個實體或表對象的實現分解為兩部分,即與模組相關的定義部分和與業務相關部分,在第一次產生時,生存器產生寫成的定義和一個空的業務代碼檔案,程式員在業務代碼檔案中增加業務處理代碼,當模型修改之後重建時只覆蓋與模型定義相關的代碼檔案。
此外,資料對象設計器還提供了基於現在資料庫產生模型的反向產生工具,目前支援ORACLE和SQLServer資料庫。
推薦的實踐
目前,大多數的中小軟體企業,都從事與資料庫相關的資訊系統的開發,項目中80-90%的業務都與資料庫相關,也算是一種典型的資料驅動開發。
AgileEAS.NET平台針這對大規模資料互動的應用提供了從資料庫設計到代碼產生、業務代碼擴充的一系列支援和實踐。
定義資料物件模型
在完成應用系統或字系統需求進入設計階段之後,AgileEAS.NET提供了資料對象設計工具兩步完成資料庫設計、資料對象定義;使用資料設計定義工具之類,項目設計階段就不再需要獨立的資料庫設計步驟,資料對象定義工具輸出資料定義語言 (Data Definition Language)DDL和資料庫設計文檔。
資料物件模型設計之後可以儲存為副檔名為.SDM的資料定義模型檔案,在定義資料模型這個環節,同步完成了資料庫的定義(表名稱、列名、資料數型、長度、小數、是否非空)、ORM實體的定義(實體名稱、屬性名稱、類型、標題)以及資料庫表和ORM實體的關係(列名--->屬性名稱)。
有有資料庫表的中繼資料定義,我們就可以產生資料庫設計文檔、資料庫定義語言DDL,有了ORM實體定義的中繼資料定義,在編輯階段就可以產生ORM實體代碼,實際上代碼產生器產生的是基於介面驅動層的資料訪問層代碼。
在資料物件模型的設計過程中,可以選擇設定模型的項目名稱、標題、程式設計語言(C#、VB)、命名空間、輸出目錄等產生代碼時需要的資訊,如處圖:
以及產生DDL、從資料庫產生模型時所需要的資料庫類型、串連資訊等:
產生資料庫文檔
AgileEAS.NET平台提供的資料對象設計器可以根據設計好的資料物件模型產生資料庫文檔,文檔格式如下所示:
產生資料庫指令碼
資料對象設計器可以根據設計好的資料物件模型產生創新資料庫表的指令碼,指令碼中包含建立表、主鍵、表、各欄位的說明注釋資訊,目錄可以產生SQLServer和Oracle兩種資料庫的DDL指令碼。
產生資料訪問層代碼
根據在資料庫設計階段設計好的資料物件模型產生資料訪問層代碼,所產生程式碼封裝含一個資料提供者項目、一個模型設計時設定的資料庫類型的資料訪問層實現。
ORM對象設計器產生的基於介面驅動的資料訪問層代碼採用了分布類的技術,也就是把同一個實現介面或者實現分解成了兩個或者多個檔案,產生器是這樣預設定的,把實體與資料庫相關聯的基礎定義資訊放在一個檔案中,檔案名稱中多了.Generator字元,與業務相關的放到一個檔案中,同一個項目中,.Generator的檔案放在個獨立的檔案夾Generat中,並且建議開發人員不要對此檔案做任務的變動。
對於與具體業務有一定相關性並不是簡單怕CRUD操作的業務擴充資料層代碼,開發人員可以寫在不帶.Generator字元的檔案,以保證重建代碼之後與業務相關的程式碼不被覆蓋。
連結
AgileEAS.NET平台開發指南-系列目錄
AgileEAS.NET應用開發平台介紹-文章索引
AgileEAS.NET官方網站
敏捷軟體工程實驗室
QQ群:116773358