由於之前已經嘗試使用過 EF CodeFirst CTP4,所以這次在EF4.1發布的第三天,在 OEA 架構中已經支援使用它來實現資料訪問層。而且,我們準備逐漸把原有的較量級ORM架構給替換掉,並且使用EF中的中繼資料系統來完全充當 OEA 中的 ORM 中繼資料,以便使用這些映射資訊來實現一些更多的操作。由於還沒有時間把整個 EF 的 MSDN 拿下,所以暫時只是在網上看了一些相關的文章。而最近又正好在重構 OEA 架構的中繼資料子系統,所以,這篇文章裡,我主要對 EF 的中繼資料進行一個簡單的分析。
注意,以下的分析只代表我的個人觀點。
不瞭解 EF 中繼資料的朋友,我這裡給出一篇我覺得寫得蠻不錯的查詢文章:《.NET - ADO.NET Entity Framework : Querying metadata》,大家有興趣可以看看。這次,先給出我認為 EF 的品質分析,方便以後查看,接下來的文章會進行一個更詳細的分析:
可擴充性:★★★★★
效能:★★
API易用性:★★★
模型基本概念
在整個EF的映射資訊中,分為 Object Model、Conceptual Model、Storage Model、Object-To-Conceptual Model、Conceptual-To-Storage Model 五大類。前三類是表明靜態結構資訊,後兩類表示靜態結構間的映射資訊。這五類別中繼資料,全部都由一個靈活度極強的中繼資料系統來描述。
Object Model 表示物件模型,該中繼資料說明了運行時對象的特徵,如:CLR運行時類名、屬性名稱等。
Conceptual Model 表示邏輯模型,該模型與資料庫元關、與程式無關,用於描述邏輯上的“領域模型”或者“業務模型”。
Storage Model 則表示資料庫中的靜態資訊,如:表名、列名。
而這三類模型間有許多的共通之處,例如,都可以用一個統一的概念來描述不同模型中的不同概念:用“實體類型”來描述對象中的類、資料庫中的表、概念性模型中的領域實體;用屬性來統一描述類的屬性、表的欄位、實體的屬性。所以 EF 使用一個簡單的 EntityType 來描述實體類型、用 EdmProperty 來描述實體屬性。
但是,它們之間必然存在差異。這就意味著,同樣的一個 EntityType 類型,需要支援不同的屬性。
屬性擴充
針對以上問題,EF 給出了一個可擴充屬性的設計:
MetadataItem 作為所有中繼資料類型的基類,使用集合的方式來提供了類似於 DynamicObject 一樣的屬性擴充系統。每個子中繼資料類型都通過 MetadataProperties 集合來定義/添加自己支援的屬性 MetadataProperty,該類聲明以下:
例如,StructuralType 類型中強型別屬性 Members 是成員的集合,
運行時視圖如下:
而繼續調試到基類,會發現 MetadataItem 中的 MetadataProperties 屬性集合中有一項正好就是名字為 Members,而值是恰好是剛才 5 個成員的集合:
所以,不用看源碼,我們也可以大膽地猜測,在 StructualType 中,Members 這個屬性的內部實現其實就是在基類的集合中註冊一個新的 Metadataproperty 項。
可以看出,這是一個動態屬性註冊的機制,動態語言運行時中的 DynamicObject、WPF及WWF 中的 DependencyProperty,都有類似的設計思想在其中。
這樣的機制可以讓我們不斷擴充屬性;不需要轉換為子類就能以“非反射”的方式來對各個屬性進行控制;換來的卻是屬性系統的效能相對低下。
類型擴充
第二個較大的擴充點在於:中繼資料類型是可擴充的。
在之前給出連結的文章中,可以看出,系統已經給出預設了許多中繼資料類型,它們都位於 System.Data.Metadata.Edm 命名空間中,如中給出了一些重要的類型:
當然,這並不是全部的中繼資料類型。細看前面中,MetadataItem 有一個 BuiltInTypeKind 屬性,它的類型是一個枚舉,例舉了EF中目前所有支援的中繼資料類型,不同的子中繼資料類型重寫這個屬性來返回不同的值。這個設計非常類似於 Linq 系統中 Expression 的設計,它們都在最頂層的基類中枚舉了所有的子類,以方便通過枚舉的判斷來識別運行時的類型。但是它們又不盡相同:Expression 是表示程式設計語言中的運算式,而這些運算式是固定的,我們不會也無法去對它進行擴充;但是 EF 中中繼資料卻是可以任意擴充的,這點可以從 BuiltInTypeKind 屬性的名字中看出,它表示的是“系統內建的類型”,當然,也可以從 MetadataProperty 中的屬性 PropertyKind 枚舉看出,它有兩個值:
Extended 就表示這個屬性是“非內建”的。
有了這樣的設計,理論上,我們可以在任意 dll 中擴充 EF 的中繼資料類型。而把執行個體全部都加入 MetadataItem 的集合中就可以了。
但是,這也帶來了不利的方面,例如,在進行查詢的時候,不能象一般的 API 一樣進行強型別的導航。換句話說,我拿到一個 MetadataItem 的集合,如果我不把它們轉換為子類型的話,無法進行強型別屬性的使用,而只能使用字串的匹配。所以,要對 EF 的中繼資料進行強型別查詢,首先要瞭解整個中繼資料的結構,然後藉助 Linq 中的 OfType<T> 方法來進行查詢。例如,我在上面中,使用 OfType<EdmProperty> 的方式來查詢給定類型中所有成員中的屬性列表。這也導致了效能比較差。
為什麼是這樣的設計?
作為一個架構,不可避免地要進行架構的可擴充性進行設計,而且,這往往是非常重要的。而且我認為,在 EF 的設計中,可擴充性是是中繼資料模組的首要設計目標。
這樣的靈活度要求,實出無賴:EF 作為一個通用的 ORM 架構,不但要同時描述物件模型、概念性模型、儲存模型,同時還要考慮到各種資料庫的相容,還需要保證未來可能出來的各種資料庫、各種方法、各種儲存結構都能被中繼資料系統支援並加以描述。
這樣的結構,可以把任意的資訊都設計出對應的類型,然後放入中繼資料系統中。這裡,為什麼能說任意呢,因為設計本身可以說是和 XML 格式等價,而目前 XML 作為一種通用的資料格式,基本上可以描述所有的資料。(具體為什麼和 XML 格式等價,這裡不再展開。)
結尾
擴充性對於架構來說非常重要,這樣的一個中繼資料系統設計,對於我來說,是十分有誘惑力的。我曾幾次考慮是否把 OEA 中繼資料系統設計成類似的結構。但是,最終還是沒有這樣做。原因在於,在進行系統/架構/架構設計時,各種品質屬性都需要進行權衡,不可一味地追求某一個屬性,而是應該找到適度的設計。這是一句老話,但是往往做起來很難。
程式設計是一門藝術,而權衡則是一輩子都要玩的藝術。