我对NHibernate的感受(3):有些尴尬的集合支持

我对NHibernate的感受(3):有些尴尬的集合支持 长假休息了好多那么继续谈谈我对NHibernate的感受。既然是一个ORM框架那么自然是将O这一端映射R上。至于集合是O这方面最常见也是R这一边非常容易表示的关系。例如一个问题Question可以包含多个回答Answer于是我的代码里就有这样的结构span stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffpublic class /spanspan stylecolor:#2b91afQuestion /span{ span stylecolor:#0000ffpublic virtual int /spanQuestionID { span stylecolor:#0000ffget/span; span stylecolor:#0000ffset/span; } span stylecolor:#0000ffpublic virtual string /spanName { span stylecolor:#0000ffget/span; span stylecolor:#0000ffset/span; } span stylecolor:#0000ffprivate /spanspan stylecolor:#2b91afISet/spanspan stylecolor:#2b91afAnswer/span m_answers; span stylecolor:#0000ffpublic /spanspan stylecolor:#2b91afISet/spanspan stylecolor:#2b91afAnswer/span Answers { span stylecolor:#0000ffget /span{ span stylecolor:#0000ffif /span(span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffnull/span) span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afHashedSet/spanspan stylecolor:#2b91afAnswer/span(); span stylecolor:#0000ffreturn this/span.m_answers; } span stylecolor:#0000ffprivate set /span{ span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffvalue/span; } } } span stylecolor:#0000ffpublic class /spanspan stylecolor:#2b91afAnswer /span{ span stylecolor:#0000ffpublic virtual int /spanAnswerID { span stylecolor:#0000ffget/span; span stylecolor:#0000ffset/span; } span stylecolor:#0000ffpublic virtual string /spanName { span stylecolor:#0000ffget/span; span stylecolor:#0000ffset/span; } span stylecolor:#0000ffpublic virtual /spanspan stylecolor:#2b91afQuestion /spanQuestion { span stylecolor:#0000ffget/span; span stylecolor:#0000ffset/span; } }/span/span于是这里就有个问题为什么Answers属性需要同时读写有的朋友可能会说NHibernate支持对私有变量的直接读写这样就可以对外暴露出只读的属性了。这个说法的确没错而且我已经在这里使用private set了不过这并不是我这里不满意的地方。更准确的说我的质疑是“为什么NHibernate会需要设置整个集合容器”试想一下在平时的开发中我们的操作都是向一个集合中添加/删除对象而不会傻傻地修改对象的集合属性。因为这个集合是对象自己维护的而不是交给外界去“一锅端”地设置。可以设置的容器属性并不仅仅是“感官”上的问题。假如我使用了上面代码那么我在向数据库插入数据时可能就是这样做的span stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffvar /spanquestion span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afQuestion/span(); question.Answers.Add(span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAnswer /span{ Name span stylecolor:#a31515Answer 1/span, span stylecolor:#ff0000Question question /span}); question.Answers.Add(span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAnswer /span{ Name span stylecolor:#a31515Answer 2/span, span stylecolor:#ff0000Question question /span}); span stylecolor:#008000// put it into session/span/span/span看看这两句红色的代码是不是有些多余不仅仅是多余这儿的问题在于如果可以这样自由设置Question属性的话那么我们是不是也有可能“一不小心”造成Answer与所在Question不匹配的问题呢仅仅是创建还好如果在一个场景下需要同时操作两个Question或Answer它们的关系可能就复杂了。NHibernate就是这样它需要我们手动地维护Question和Answer的双向引用否则插入/删除/更新都可能不正确。有些人的解决方法是添加额外的方法例如AddAnswerspan stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffpublic class /spanspan stylecolor:#2b91afQuestion /span{ ... span stylecolor:#0000ffpublic void /spanAddAnswer(span stylecolor:#2b91afAnswer /spananswer) { span stylecolor:#0000ffif /span(answer.Question ! span stylecolor:#0000ffnull/span) { answer.Question.Answers.Remove(answer); } answer.Question span stylecolor:#0000ffthis/span; span stylecolor:#0000ffthis/span.Answers.Add(answer); } }/span/span使用AddAnswer方法便可以自动地剥离Answer与原有Question的关系并且与新的Question建立联系了。同理从一个Question对象中删除一个Answer对象或者修改Answer对象的Question属性应该都会引起双方关系的变化。但是即便我们提供了完整的关系维护手段Question.Answers还是对外暴露开发人员还是可以修改Answers集合。因此最好的办法其实应该是在集合中提供一种维护关系的方式。例如LINQ to SQL在这一点上便做的不错span stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffpublic partial class /spanspan stylecolor:#2b91afQuestion/span { span stylecolor:#0000ffprivate static /spanspan stylecolor:#2b91afPropertyChangingEventArgs /spanemptyChangingEventArgs span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afPropertyChangingEventArgs/span(span stylecolor:#2b91afString/span.Empty); span stylecolor:#0000ffprivate int /span_QuestionID; span stylecolor:#0000ffprivate string /span_Name; span stylecolor:#0000ffprivate /spanspan stylecolor:#2b91afEntitySet/spanspan stylecolor:#2b91afAnswer/span _Answers; span stylecolor:#0000ff public /spanQuestion() { span stylecolor:#0000ffthis/span._Answers span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afEntitySet/spanspan stylecolor:#2b91afAnswer/span( span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAction/spanspan stylecolor:#2b91afAnswer/span(span stylecolor:#0000ffthis/span.attach_Answers), span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAction/spanspan stylecolor:#2b91afAnswer/span(span stylecolor:#0000ffthis/span.detach_Answers)); } span stylecolor:#0000ffpublic int /spanQuestionID { ... } span stylecolor:#0000ffpublic string /spanName { ... } span stylecolor:#0000ffpublic /spanspan stylecolor:#2b91afEntitySet/spanspan stylecolor:#2b91afAnswer/span Answers { span stylecolor:#0000ffget /span{ span stylecolor:#0000ffreturn this/span._Answers; } span stylecolor:#0000ffset /span{ span stylecolor:#0000ffthis/span._Answers.Assign(span stylecolor:#0000ffvalue/span); } } span stylecolor:#0000ffprivate void /spanattach_Answers(span stylecolor:#2b91afAnswer /spanentity) { entity.Question span stylecolor:#0000ffthis/span; } span stylecolor:#0000ffprivate void /spandetach_Answers(span stylecolor:#2b91afAnswer /spanentity) { entity.Question span stylecolor:#0000ffnull/span; } }/span/span看看LINQ to SQL对我们多体贴自动生成的代码会帮我们维护Question与Answer之间的双向关系。当然还有一部分逻辑是在Answer类的Question属性中如果您感兴趣可以自己去观察一下。不过LINQ to SQL的问题在于它使用了特殊的类型EntitySet它会使用两个回调函数对外公布集合内元素的添加/删除情况。按理来说如果我们想要在NHibernate中采用这种“自动维护”的方式可以使用自定义的集合类型例如span stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffprivate /spanspan stylecolor:#2b91afISet/spanspan stylecolor:#2b91afAnswer/span m_answers; span stylecolor:#0000ffpublic /spanspan stylecolor:#2b91afISet/spanspan stylecolor:#2b91afAnswer/span Answers { span stylecolor:#0000ffget /span{ span stylecolor:#0000ffif /span(span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffnull/span) span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afCallbackSet/spanspan stylecolor:#2b91afAnswer/span(...); span stylecolor:#0000ffreturn this/span.m_answers; } span stylecolor:#0000ffprivate set /span{ span stylecolor:#0000ffthis/span.m_answers span stylecolor:#0000ffvalue/span; } }/span/span只可惜在新建对象的时候我们自然利用到CallbackSetAnswer其中包含了我们定义的逻辑。但是如果是这样的代码呢span stylecolor:#333333span stylebackground-color:#ffffffspan stylecolor:#0000ffvar /spanquestion session.Getspan stylecolor:#2b91afQuestion/span(1); question.Answers.Add(span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAnswer /span{ Name span stylecolor:#a31515Answer 1/span, Question question }); question.Answers.Add(span stylecolor:#0000ffnew /spanspan stylecolor:#2b91afAnswer /span{ Name span stylecolor:#a31515Answer 2/span, Question question }); session.Flush();/span/span在从数据库中获取Question对象的时候NHibernate便会“自作主张”地将Answers属性“整个”设为自己的ISetAnswer对象——因为实现延迟加载它也并不一定是HashedSetAnswer。换句话说NHibernate虽然能够保持属性的逻辑但它不能保持自定义集合的逻辑。在我看来NHibernate完全可以做到放弃集合属性的set操作把所有的对象都通过集合的Add方法添加进去。其实这样做同样可以实现集合的延迟加载就好比放弃对所有方法的强制virtual要求也能实现对象的延迟加载一样。为了避免像上次那样误解NHibernate我刚才又作了一次测试——这次我应该没有搞错。当然如果NHibernate支持对自定义集合类型那就再好不过了我们就有办法解决这个问题。但是我不知道该怎么做如果您知道的话请告诉我。在我看来目前的问题是NHibernate对于POCO支持有缺陷造成的。如果是这样的话那我们的Model就不得不继续迁就NHibernate了。关于NHibernate集合还有一个有趣的问题是——请关注上面这4行代码Get-Add-Flush这段这是一个非常标准也是非常常见的添加Answer对象的方式。只可惜在调用ISetAnswer的Add方法添加Answer对象的时候会引发一次数据库查询操作加载当前Question下的所有Answer——但是在我看来这根本没有必要啊。我只是“添加”并没有要查询。其实NHibernate帮我把新的Answer对象保存起来就可以了为什么要增加无畏的开销呢当然我承认这个做法会产生一些麻烦例如需要将集合的操作分为“读”和“写”两类当“写”操作发生时不会加载数据而只有在第一次“读”的时候才去数据库查询。“读”和“写”分离本来就应该这样。那么谁又做到这一点了呢又是LINQ to SQL。其实LINQ to SQL在细节上有非常多的考虑使用起来也是非常容易的——如果我不是被它“宠坏”的话可能也就不会在意NHiberante的这个问题了。只可惜对于ORM的生命“映射方式”上LINQ to SQL的支持过于有限这也大大限制了项目对它的接受程度。