NHibernate自定义集合类型(上):基本实现方式
前天一篇文章中我說NHibernate的集合類型實現(xiàn)有些“尷尬”,它無法使用自定義集合類型,設(shè)計也有些古怪——不過在許多朋友的指點下,我意識到NHibernate是可以使用自定義集合類型的。至于它的設(shè)計是否合理(或者說是用是否方便?)……這就是這幾篇文章中想要探討的內(nèi)容了。不少朋友給出了一些自定義集合類型的示例鏈接,我參考之余也自己找了一些資料,慢慢嘗試,也終于有了一些體會。
這個小系列預(yù)計有上中下三篇,在這第一篇里主要是闡述在NHibernate中自定義集合類型的基本原理和方式,進而引發(fā)一些問題。第二篇主要便是解決問題,并為了簡化開發(fā)提供一個思路和“通用”一些的實現(xiàn)。至于第三篇,便是一個“示例”,目的便是在領(lǐng)域模型中為一對多的雙方維護雙向的關(guān)系了。搞這些東西讓我頭大,因為資料實在太少,就算有也大多數(shù)是淺嘗輒止,沒有多少“通用”的東西,有些呢又過于復(fù)雜(在我看來也違背了一些“設(shè)計原則”),忽然找到一個似乎是有示例有詳細說明的文章,卻因為圖片和代碼全部丟失讓我空歡喜一場。總而言之,我這幾篇是在參考零散資料的基礎(chǔ)上,“連猜帶蒙”又經(jīng)歷了無數(shù)嘗試和挫敗總結(jié)出的結(jié)果——當(dāng)然,您會發(fā)現(xiàn),其實還很不徹底。
有時候我也想,難道各位用Hibernate和NHibernate的同志真沒有遇到我需要的場景,真沒有像我一樣考慮這么多嗎?還是我的想法過于古怪,實際上不會這么做?否則為什么互聯(lián)網(wǎng)上的資料那么少……
言歸正傳,我們開始自定義一個集合。作為基本實現(xiàn)方式的演示,我還是打算使用上一篇文章中Question和Answer的一對多關(guān)系作為示例:
public class Question {public virtual int QuestionID { get; set; }public virtual string Name { get; set; }private ISet<Answer> m_answers;public virtual ISet<Answer> Answers{get{if (this.m_answers == null)this.m_answers = new HashedSet<Answer>();return this.m_answers;}set{this.m_answers = value;}} }public class Answer {public virtual int AnswerID { get; set; }public virtual string Name { get; set; }public virtual Question Question { get; set; } }Question的Answers屬性是ISet<Answer>類型,但是這個集合類型太單薄了,我需要它包含一些輔助邏輯和功能都不行(擴展方法不是萬能的),那么讓我們來擴展它,讓Question的Answers集合使用我們自定義的類型吧:
public class Question {...private IAnswerSet m_answers;public virtual IAnswerSet Answers{get{if (this.m_answers == null)this.m_answers = new AnswerSet();return this.m_answers;}set{this.m_answers = value;}} }public interface IAnswerSet : ISet<Answer> {int Calculate(); }public class AnswerSet : HashedSet<Answer>, IAnswerSet {public virtual int Calculate() { return 0; } }我們基于ISet<Answer>擴展了了一個IAnswerSet,并提供了一個Calculate方法(抱歉在這里我找不出合適的示例)。作為IAnswerSet的默認實現(xiàn),我們也實現(xiàn)了AnswerSet類,它繼承了HashedSet<Answer>,因此也只需要實現(xiàn)額外的Calculate方法就可以了。這兩個類非常簡單。
不過,NHibernate又如何知道該怎么使用AnswerSet呢?那就需要我們提供一個IUserCollectionType來告訴它這些信息了:
public class AnswerSetType : IUserCollectionType {#region IUserCollectionType Memberspublic bool Contains(object collection, object entity){return ((IAnswerSet)collection).Contains((Answer)entity);}public IEnumerable GetElements(object collection){return (IEnumerable)collection;}public object IndexOf(object collection, object entity){throw new NotImplementedException(); // 作為Set不需要這個方法}public object Instantiate(int anticipatedSize){return new AnswerSet();}public IPersistentCollection Instantiate(ISessionImplementor session, ...){return new PersistentAnswerSet(session);}public object ReplaceElements(object original, object target, ...){var result = (AnswerSet)target;result.Clear();foreach (var item in (IEnumerable<Answer>)original){result.Add(item);}return result;}public IPersistentCollection Wrap(ISessionImplementor session, object collection){return new PersistentAnswerSet(session, (IAnswerSet)collection);}#endregion }我們要為AnswerSet實現(xiàn)一個AnswerSetType類型才能告訴NHibernate該如何使用IAnswerSet類型。AnswerSetType的大部分方法都是淺顯易懂的,不作贅述。不過有一些東西可能不是那么明白:
- IPersistentCollection是什么?
- PersistentAnswerSet又是什么?
- 返回IPersistentCollection的Instantiate和Wrap方法又是什么?
這就涉及到NHibernate的一個重要功能了:自動跟蹤集合狀態(tài)。說是自動,其實當(dāng)然還是要我們?nèi)ジ嬖V它“集合做出了哪些改變”的。怎么告訴呢?向ISessionImplemetor對象提供信息即可。那么怎么提供信息呢?通過IPersistentCollection。
這里又有一個“話題”,那就是為什么NHibernate一定要(還是“建議”?)我們?yōu)榧项愋吞峁┮粋€“接口”,而不是個具體類?這是因為它需要為這個接口使用不同的實現(xiàn),來做到延遲加載,亦或是“跟蹤集合狀態(tài)”。例如下面的代碼:
var session = ...;var question = new Question { Name = "Question A" }; question.Answers.Add(new Answer { Name = "Answer A - 1", Question = question }); question.Answers.Add(new Answer { Name = "Answer A - 2", Question = question });session.Save(question); session.Flush();您認為,在question對象被保存進數(shù)據(jù)庫之后,它的Answers集合是什么具體類型的呢?是AnswerSet嗎?錯了,它已經(jīng)被替換成為PersistentAnswerSet類型(經(jīng)過AnswerSetType.Wrap方法封裝過)了。PersisitentAnswerSet的作用便是在提供了IAnswerSet的功能以外,還實現(xiàn)了IPersistentCollection接口,“同時”為NHibernate提供了“持久化信息”。很顯然的是,PersistentAnswerSet和AnswerSet在IAnswerSet接口的功能上應(yīng)該完全相同,否則前者就無法替代后者了。因此,PersistentAnswerSet理想的實現(xiàn)應(yīng)該是這樣的:
public class PersisitentAnswerSet : AnswerSet, IPersistentCollection { ... }那么我們又該如何實現(xiàn)IPersistentCollection接口呢?別急,先來看看它有哪些成員吧。嗯……什么,33個方法和12個屬性?沒錯,IPersistentCollection便是這么一個龐然大物,因為它要為NHibernate提供太多信息了。比如,從上次保存之后弄臟了沒有?添加過哪些元素,又刪了哪些元素?太多太多了。而且,這些成員的作用是什么呢?也基本沒有資料可以告訴我們必要的信息,似乎唯一的做法便是閱讀代碼了。因此,這簡直叫人沒法實現(xiàn)。
“幸運的是”,NHiberante內(nèi)部提供了一個PersistentGenericSet<T>類,實現(xiàn)了ISet<T>所需的持久化操作。于是我們的PersistentAnswerSet可以基于它來實現(xiàn):
public class PersistentAnswerSet : PersistentGenericSet<Answer>, IAnswerSet { public PersistentAnswerSet(ISessionImplementor session): base(session){}public PersistentAnswerSet(ISessionImplementor session, IAnswerSet collection) :base(session, collection){}public virtual int Calculate() { return 0; } }只可惜,為了保持和AnswerSet行為完全一致,我們必須在PersistentAnswerSet中也提供一個一模一樣的Calculate方法了——如果AnswerSet還有其他實現(xiàn),或者重寫了ISet<Answer>的Add,Remove等方法,我們在PersistentAnswerSet中也必須一一照辦。這是一種臭不可聞的重復(fù)。
有人可能會說,那么我們不要AnswerSet類了,直接在PersistentAnswerSet提供IAnswerSet的行為,然后就在Question類中給出PersistentAnswerSet,不可以嗎?從實現(xiàn)角度,可行。但是從設(shè)計角度上來說,不可取。因為Question是我們的領(lǐng)域模型,而PersistentAnswerSet依賴著NHibernate的持久化邏輯。如果在Question中直接使用PersistentAnswerSet,這就產(chǎn)生了領(lǐng)域模型到持久化邏輯的依賴了——這從領(lǐng)域模型設(shè)計起初就是一直在避免的。
從以上的示例中也可以看出,自定義集合的關(guān)鍵是在于提供一個IUserCollectionType以及一個IPersistentCollection對象。有了這兩個保證,無論是Set,Bag,List還是其他任何的類型,從理論上來說,NHibernate都是支持的。但是事實上,幾乎沒有人去這么做。因為其中的設(shè)計有一些很古怪的,難以捉摸的地方。例如我除了基于Set的自定義集合之外,還嘗試了基于Bag的開發(fā),但是可謂困難重重。
Bag是基于Collection的,PersistentGenericBag<T>構(gòu)造函數(shù)接受的參數(shù)也是ICollection<T>,但是在PersistentGenericBag<T>內(nèi)部卻會將集合轉(zhuǎn)化為ICollection——它和ICollection<T>可沒有任何聯(lián)系(不像IEnumerable<T>是基于IEnumerable的)。因此我強烈懷疑PersistentGenericBag只是在Java Hibernate的非泛型基礎(chǔ)上,包裝的一層機械封裝而已。此外,在從集合內(nèi)部刪除元素并保存至數(shù)據(jù)庫的時候,NHibernate還會嘗試將我們的集合轉(zhuǎn)化為IList類型。真是奇怪的做法。至于它對List的支持,對普通自定義集合的支持(這要求我們實現(xiàn)IPersistentCollection的45個成員)我就沒有嘗試了。說實話,我不信任NHibernate除Set以外的集合類型。以前在使用List的時候,也發(fā)現(xiàn)它的映射關(guān)系并不如文檔上寫的那么“符合List語義”。如果您感興趣的話,我們也可以對這方面進行更多探討和嘗試。
還是回到AnswerSet吧,要使用IAnswerSet自定義集合類型,還需要進行配置。用Fluent NHibernate來寫,它可能就是這樣的:
public class QuestionMap : ClassMap<Question> { public QuestionMap(){Id(q => q.QuestionID).GeneratedBy.Identity();Map(q => q.Name);HasMany(q => q.Answers).LazyLoad().CollectionType<AnswerSetType>().KeyColumns.Add("QuestionID").Cascade.All().Inverse();} }目前我們的IAnswerSet支持向集合內(nèi)添加元素,刪除元素并保存,以及延遲加載,滿足我基本操作的要求。不過以上還只是“基本實現(xiàn)方式”,在投入生產(chǎn)之前我們還是有兩個問題必須解決:
這真的很不容易,下次我們再來設(shè)法解決這個問題。
相關(guān)文章
- NHibernate自定義集合類型(上):基本實現(xiàn)方式
- NHibernate自定義集合類型(中):通用實現(xiàn)方式
- NHibernate自定義集合類型(下):自動維護雙向關(guān)系
轉(zhuǎn)載于:https://www.cnblogs.com/JeffreyZhao/archive/2009/10/10/nhibernate-custom-collection-1-basics.html
總結(jié)
以上是生活随笔為你收集整理的NHibernate自定义集合类型(上):基本实现方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何看Linux服务器是否被攻击
- 下一篇: sharepoint配置问题解决方案