NHibernate自定义集合类型(上):基本实现方式

发布时间:2026/7/6 3:46:19
NHibernate自定义集合类型(上):基本实现方式
这个小系列预计有上中下三篇在这第一篇里主要是阐述在NHibernate中自定义集合类型的基本原理和方式进而引发一些问题。第二篇主要便是解决问题并为了简化开发提供一个思路和“通用”一些的实现。至于第三篇便是一个“示例”目的便是在领域模型中为一对多的双方维护双向的关系了。搞这些东西让我头大因为资料实在太少就算有也大多数是浅尝辄止没有多少“通用”的东西有些呢又过于复杂在我看来也违背了一些“设计原则”忽然找到一个似乎是有示例有详细说明的文章却因为图片和代码全部丢失让我空欢喜一场。总而言之我这几篇是在参考零散资料的基础上“连猜带蒙”又经历了无数尝试和挫败总结出的结果——当然您会发现其实还很不彻底。有时候我也想难道各位用Hibernate和NHibernate的同志真没有遇到我需要的场景真没有像我一样考虑这么多吗还是我的想法过于古怪实际上不会这么做否则为什么互联网上的资料那么少……言归正传我们开始自定义一个集合。作为基本实现方式的演示我还是打算使用上一篇文章中Question和Answer的一对多关系作为示例public class Question { public virtual int QuestionID { get; set; } public virtual string Name { get; set; } private ISetAnswer m_answers; public virtual ISetAnswer Answers { get { if (this.m_answers null) this.m_answers new HashedSetAnswer(); 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属性是ISetAnswer类型但是这个集合类型太单薄了我需要它包含一些辅助逻辑和功能都不行扩展方法不是万能的那么让我们来扩展它让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 : ISetAnswer { int Calculate(); } public class AnswerSet : HashedSetAnswer, IAnswerSet { public virtual int Calculate() { return 0; } }我们基于ISetAnswer扩展了了一个IAnswerSet并提供了一个Calculate方法抱歉在这里我找不出合适的示例。作为IAnswerSet的默认实现我们也实现了AnswerSet类它继承了HashedSetAnswer因此也只需要实现额外的Calculate方法就可以了。这两个类非常简单。不过NHibernate又如何知道该怎么使用AnswerSet呢那就需要我们提供一个IUserCollectionType来告诉它这些信息了public class AnswerSetType : IUserCollectionType { #region IUserCollectionType Members public 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 (IEnumerableAnswer)original) { result.Add(item); } return result; } public IPersistentCollection Wrap(ISessionImplementor session, object collection) { return new PersistentAnswerSet(session, (IAnswerSet)collection); } #endregion }我们要为AnswerSet实现一个AnswerSetType类型才能告诉NHibernate该如何使用IAnswerSet类型。AnswerSetType的大部分方法都是浅显易懂的不作赘述。不过有一些东西可能不是那么明白IPersistentCollection是什么PersistentAnswerSet又是什么返回IPersistentCollection的Instantiate和Wrap方法又是什么这就涉及到NHibernate的一个重要功能了自动跟踪集合状态。说是自动其实当然还是要我们去告诉它“集合做出了哪些改变”的。怎么告诉呢向ISessionImplemetor对象提供信息即可。那么怎么提供信息呢通过IPersistentCollection。这里又有一个“话题”那就是为什么NHibernate一定要还是“建议”我们为集合类型提供一个“接口”而不是个具体类这是因为它需要为这个接口使用不同的实现来做到延迟加载亦或是“跟踪集合状态”。例如下面的代码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对象被保存进数据库之后它的Answers集合是什么具体类型的呢是AnswerSet吗错了它已经被替换成为PersistentAnswerSet类型经过AnswerSetType.Wrap方法封装过了。PersisitentAnswerSet的作用便是在提供了IAnswerSet的功能以外还实现了IPersistentCollection接口“同时”为NHibernate提供了“持久化信息”。很显然的是PersistentAnswerSet和AnswerSet在IAnswerSet接口的功能上应该完全相同否则前者就无法替代后者了。因此PersistentAnswerSet理想的实现应该是这样的public class PersisitentAnswerSet : AnswerSet, IPersistentCollection { ... }那么我们又该如何实现IPersistentCollection接口呢别急先来看看它有哪些成员吧。嗯……什么33个方法和12个属性没错IPersistentCollection便是这么一个庞然大物因为它要为NHibernate提供太多信息了。比如从上次保存之后弄脏了没有添加过哪些元素又删了哪些元素太多太多了。而且这些成员的作用是什么呢也基本没有资料可以告诉我们必要的信息似乎唯一的做法便是阅读代码了。因此这简直叫人没法实现。“幸运的是”NHiberante内部提供了一个PersistentGenericSetT类实现了ISetT所需的持久化操作。于是我们的PersistentAnswerSet可以基于它来实现public class PersistentAnswerSet : PersistentGenericSetAnswer, IAnswerSet { public PersistentAnswerSet(ISessionImplementor session) : base(session) { } public PersistentAnswerSet(ISessionImplementor session, IAnswerSet collection) : base(session, collection) { } public virtual int Calculate() { return 0; }