找回密码
 立即注册
首页 业界区 业界 NHibernate自定义集合类型(下):自动维护双向关系 ...

NHibernate自定义集合类型(下):自动维护双向关系

轮达 2025-5-29 14:57:40
如果使用NHibernate自带的集合类型,其中一个问题就在于需要在代码中手动维护双向关系,迫使开发人员编写额外的代码。其实这就是集合自定义逻辑的一个应用方面。现在,既然我们已经得到了一个方便的自定义集合的解决方案,那么现在便把“自动维护双向关系”作为目标来实现一番,也算是一个非常典型的示例了。
昨天是休息天,看文章的朋友比较少,如果您遗漏了上一篇的内容,不妨再阅读一次,对理解本文会有一定帮助。
我们已经知道LINQ to SQL是如何自动维护双向关系的,它的做法是在集合被添加或删除元素时发起一个回调函数,而在回调函数内部对某些属性进行设置。我们也可以采用这种方式。不过在此之前,我们必须知道NHibernate在进行集合操作时的一些顺序,例如在加载父实体时,集合属性的set操作和集合元素的添加操作哪个在前,哪个在后。
我们还是使用Question-Answer作为示例:
  1. public class Question
  2. {
  3.     public virtual int QuestionID { get; set; }
  4.     public virtual string Name { get; set; }
  5.     private ISet m_answers;
  6.     public virtual ISet Answers
  7.     {
  8.         get
  9.         {
  10.             if (this.m_answers == null)
  11.                 this.m_answers = new AnswerSet();
  12.             return this.m_answers;
  13.         }
  14.         set
  15.         {
  16.             Console.WriteLine("Set Question.Answers");
  17.             this.m_answers = value;
  18.         }
  19.     }
  20. }
  21. public class AnswerSet : HashedSet
  22. {
  23.     public override bool Add(Answer o)
  24.     {
  25.         if (base.Add(o))
  26.         {
  27.             Console.WriteLine("Add Answer");
  28.             return true;
  29.         }
  30.         return false;
  31.     }
  32. }
复制代码
我们在Question.Answers属性的Set操作与AnswerSet的Add操作中都添加了一些输出文本的代码。然后,我们使用下面的代码进行测试:
  1. [Fact]
  2. public void LazyLoad()
  3. {
  4.     var session = SessionFactory.Instance.OpenSession();
  5.     var question = session.Get<Question>(1);
  6.     question.Answers.Add(new Answer { Name = "Answer", Question = question });
  7. }
  8. [Fact]
  9. public void EagerLoad()
  10. {
  11.     var session = SessionFactory.Instance.OpenSession();
  12.     var question = session
  13.         .CreateCriteria<Question>()
  14.         .Add(Expression.IdEq(1))
  15.         .SetFetchMode("Answers", FetchMode.Eager)
  16.         .UniqueResult<Question>();
  17.     question.Answers.Add(new Answer { Name = "Answer", Question = question });
  18. }
复制代码
LazyLoad和EagerLoad分别测试的是延迟加载和“饥渴”加载两种情况下的操作顺序。从输出内容里可以发现,两种情况下结果完全相同:
  1. Set Question.Answers
  2. Add Answer
  3. Add Answer
  4. Add Answer
复制代码
由于原本数据库中该Question有2个Answer对象,因此会输出三条Add Answer信息。可以看出,NHibernate会先设置集合类型,再向其中添加元素。这对我们来说是一个好消息,因为我们可以放心地在Question.Answers的set操作中添加回调函数,然后等待Answer对象一个个添加进来,我们的逻辑为它们一一建立关联。
为了“回调”,我们可以定义一个通用的ObservableSet:
  1. public enum ItemChangedType
  2. {
  3.     Added,
  4.     Removed
  5. }
  6. public class ItemChangedEventArgs<T> : EventArgs
  7. {
  8.     public ItemChangedEventArgs(ItemChangedType type, T item)
  9.     {
  10.         this.Type = type;
  11.         this.Item = item;
  12.     }
  13.     public ItemChangedType Type { get; private set; }
  14.     public T Item { get; private set; }
  15. }
  16. public interface IObservableSet<T> : ISet<T>
  17. {
  18.     event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
  19. }
  20. public class ObservableSet<T> : HashedSet<T>, IObservableSet<T>
  21. {
  22.     public override bool Add(T o)
  23.     {
  24.         if (base.Add(o))
  25.         {
  26.             var e = new ItemChangedEventArgs<T>(ItemChangedType.Added, o);
  27.             this.OnItemChanged(e);
  28.             return true;
  29.         }
  30.         return false;
  31.     }
  32.     public override bool Remove(T o)
  33.     {
  34.         if (base.Remove(o))
  35.         {
  36.             var e = new ItemChangedEventArgs<T>(ItemChangedType.Removed, o);
  37.             this.OnItemChanged(e);
  38.             return true;
  39.         }
  40.         return false;
  41.     }
  42.     public event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
  43.     protected void OnItemChanged(ItemChangedEventArgs<T> e)
  44.     {
  45.         var itemChanged = this.ItemChanged;
  46.         if (itemChanged != null) itemChanged(this, e);
  47.     }
  48. }
复制代码
显然,修改一个HashedSet元素内容的接口不止Add和Remove两个方法,于是在override的时候就又要缩手缩脚了。最终,我还是通过阅读HashedSet的源代码才意识到所有的接口最终都会调用Add和Remove方法。因此,我们只需要在Add和Remove的时候发起事件即可。于是在Question对象中:
  1. private IObservableSet m_answers;
  2. public virtual IObservableSet Answers
  3. {
  4.     get
  5.     {
  6.         if (this.m_answers == null)
  7.         {
  8.             this.Answers = new ObservableSet();
  9.         }
  10.         return this.m_answers;
  11.     }
  12.     set
  13.     {
  14.         this.ChangeAnswerSet(value);
  15.     }
  16. }
  17. private EventHandler<ItemChangedEventArgs> m_answerSetItemChangedHandler;
  18. private void ChangeAnswerSet(IObservableSet answers)
  19. {
  20.     if (this.m_answerSetItemChangedHandler == null)
  21.     {
  22.         this.m_answerSetItemChangedHandler = this.OnAnswerSetItemChanged;
  23.     }
  24.     if (this.m_answers != null)
  25.     {
  26.         this.m_answers.ItemChanged -= this.m_answerSetItemChangedHandler;
  27.     }
  28.     this.m_answers = answers;
  29.     this.m_answers.ItemChanged += this.m_answerSetItemChangedHandler;
  30. }
  31. private void OnAnswerSetItemChanged(object sender, ItemChangedEventArgs e)
  32. {
  33.     ...
  34. }
复制代码
目前,在设置Question.Answers属性的时候,我们会为它添加事件处理函数,并且将旧的事件处理函数剥离。至于OnAnswerSetItemChanged中,便是维护Answer和Question的关系了:
  1. private void OnAnswerSetItemChanged(object sender, ItemChangedEventArgs e)
  2. {
  3.     var answer = e.Item;
  4.     if (e.Type == ItemChangedType.Added)
  5.     {
  6.         if (answer.Question != null)
  7.         {
  8.             answer.Question.Answers.Remove(answer);
  9.         }
  10.         answer.Question = this;
  11.     }
  12.     else
  13.     {
  14.         answer.Question = null;
  15.     }
  16. }
复制代码
至此,我们已经在Answer元素添加至Question.Answers集合时保持了逻辑。但是,我们还有两个东西没有搞定:

  • 如果外界有代码直接一锅端地设置Question.Answers集合,那么旧集合中的元素是否要和Question脱离关系?
  • 如果外界有人直接设置Answer.Question属性,是否要将其添加到Question.Answers集合中?
严格说来,这些都是需要的。但是我在这里不选择这种做法,我选择——将Question.Answers集合和Answer.Question属性的set方法都设置为private!在OnAnswerSetItemChanged方法内部,就动用FastReflectionLib来设置answer.Question属性。至于NHibernate,它的操作都是可靠的,我们信任它。您想想看,这么做有什么问题吗?似乎真没有。我们在任何需要设置Answer.Question属性的地方,都可以通过操作Question.Answers集合来实现。
最后的配置自然也是必不可少的:
  1. public class QuestionMap : ClassMap<Question>
  2. {
  3.     public QuestionMap()
  4.     {
  5.         Id(q => q.QuestionID).GeneratedBy.Identity();
  6.         Map(q => q.Name);
  7.         HasMany(q => q.Answers)
  8.             .LazyLoad()
  9.             <font color="#ff0000">.CollectionType</font><SetType<Answer, ObservableSet, IObservableSet>>()
  10.             .KeyColumns.Add("QuestionID")
  11.             .Cascade.All()
  12.             .Inverse();
  13.     }
  14. }
复制代码
当然,您是否觉得现在自动保持双向关系的做法非常繁琐?
的确如此啊,那么,您是否可以解决(至少缓解)这个问题?
相关文章


  • NHibernate自定义集合类型(上):基本实现方式
  • NHibernate自定义集合类型(中):通用实现方式
  • NHibernate自定义集合类型(下):自动维护双向关系

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册