天天看點

EntityFramework 中繼資料 設計分析

   由于之前已經嘗試使用過 ef codefirst ctp4,是以這次在ef4.1釋出的第三天,在 oea 架構中已經支援使用它來實作資料通路層。而且,我們準備逐漸把原有的較量級orm架構給替換掉,并且使用ef中的中繼資料系統來完全充當 oea 中的 orm 中繼資料,以便使用這些映射資訊來實作一些更多的操作。由于還沒有時間把整個 ef 的 msdn 拿下,是以暫時隻是在網上看了一些相關的文章。而最近又正好在重構 oea 架構的中繼資料子系統,是以,這篇文章裡,我主要對 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 給出了一個可擴充屬性的設計:

EntityFramework 中繼資料 設計分析

metadataitem 作為所有中繼資料類型的基類,使用集合的方式來提供了類似于 dynamicobject 一樣的屬性擴充系統。每個子中繼資料類型都通過 metadataproperties 集合來定義/添加自己支援的屬性 metadataproperty,該類聲明以下:

EntityFramework 中繼資料 設計分析

例如,structuraltype 類型中強類型屬性 members 是成員的集合,

EntityFramework 中繼資料 設計分析

運作時視圖如下:

EntityFramework 中繼資料 設計分析

而繼續調試到基類,會發現 metadataitem 中的 metadataproperties 屬性集合中有一項正好就是名字為 members,而值是恰好是剛才 5 個成員的集合:

EntityFramework 中繼資料 設計分析

是以,不用看源碼,我們也可以大膽地猜測,在 structualtype 中,members 這個屬性的内部實作其實就是在基類的集合中注冊一個新的 metadataproperty 項。

可以看出,這是一個動态屬性注冊的機制,動态語言運作時中的 dynamicobject、wpf及wwf 中的 dependencyproperty,都有類似的設計思想在其中。

    這樣的機制可以讓我們不斷擴充屬性;不需要轉換為子類就能以“非反射”的方式來對各個屬性進行控制;換來的卻是屬性系統的性能相對低下。

類型擴充

    第二個較大的擴充點在于:中繼資料類型是可擴充的。

    在之前給對外連結接的文章中,可以看出,系統已經給出預設了許多中繼資料類型,它們都位于 system.data.metadata.edm 命名空間中,如下圖中給出了一些重要的類型:

EntityFramework 中繼資料 設計分析

當然,這并不是全部的中繼資料類型。細看前面截圖中,metadataitem 有一個 builtintypekind 屬性,它的類型是一個枚舉,例舉了ef中目前所有支援的中繼資料類型,不同的子中繼資料類型重寫這個屬性來傳回不同的值。這個設計非常類似于 linq 系統中 expression 的設計,它們都在最頂層的基類中枚舉了所有的子類,以友善通過枚舉的判斷來識别運作時的類型。但是它們又不盡相同:expression 是表示程式設計語言中的表達式,而這些表達式是固定的,我們不會也無法去對它進行擴充;但是 ef 中中繼資料卻是可以任意擴充的,這點可以從 builtintypekind 屬性的名字中看出,它表示的是“系統内置的類型”,當然,也可以從 metadataproperty 中的屬性 propertykind 枚舉看出,它有兩個值:

EntityFramework 中繼資料 設計分析

extended 就表示這個屬性是“非内置”的。

    有了這樣的設計,理論上,我們可以在任意 dll 中擴充 ef 的中繼資料類型。而把執行個體全部都加入 metadataitem 的集合中就可以了。

    但是,這也帶來了不利的方面,例如,在進行查詢的時候,不能象一般的 api 一樣進行強類型的導航。換句話說,我拿到一個 metadataitem 的集合,如果我不把它們轉換為子類型的話,無法進行強類型屬性的使用,而隻能使用字元串的比對。是以,要對 ef 的中繼資料進行強類型查詢,首先要了解整個中繼資料的結構,然後借助 linq 中的 oftype<t> 方法來進行查詢。例如,我在上面截圖中,使用 oftype<edmproperty> 的方式來查詢給定類型中所有成員中的屬性清單。這也導緻了性能比較差。

為什麼是這樣的設計?

    作為一個架構,不可避免地要進行架構的可擴充性進行設計,而且,這往往是非常重要的。而且我認為,在 ef 的設計中,可擴充性是是中繼資料子產品的首要設計目标。

    這樣的靈活度要求,實出無賴:ef 作為一個通用的 orm 架構,不但要同時描述對象模型、概念模型、存儲模型,同時還要考慮到各種資料庫的相容,還需要保證未來可能出來的各種資料庫、各種方法、各種存儲結構都能被中繼資料系統支援并加以描述。

    這樣的結構,可以把任意的資訊都設計出對應的類型,然後放入中繼資料系統中。這裡,為什麼能說任意呢,因為設計本身可以說是和 xml 格式等價,而目前 xml 作為一種通用的資料格式,基本上可以描述所有的資料。(具體為什麼和 xml 格式等價,這裡不再展開。)

結尾

    擴充性對于架構來說非常重要,這樣的一個中繼資料系統設計,對于我來說,是十分有誘惑力的。我曾幾次考慮是否把 oea 中繼資料系統設計成類似的結構。但是,最終還是沒有這樣做。原因在于,在進行系統/架構/架構設計時,各種品質屬性都需要進行權衡,不可一味地追求某一個屬性,而是應該找到适度的設計。這是一句老話,但是往往做起來很難。

    程式設計是一門藝術,而權衡則是一輩子都要玩的藝術。