尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

我对NHibernate的感受(3):有些尴尬的集合支持
📅 发布时间:2026/7/6 3:45:59

长假休息了好多,那么继续谈谈我对NHibernate的感受。

既然是一个ORM框架,那么自然是将O这一端映射R上。至于集合,是O这方面最常见,也是R这一边非常容易表示的关系。例如,一个问题(Question)可以包含多个回答(Answer),于是我的代码里就有这样的结构:

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">public class </span><span style="color:#2b91af">Question </span>{ <span style="color:#0000ff">public virtual int </span>QuestionID { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; } <span style="color:#0000ff">public virtual string </span>Name { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; } <span style="color:#0000ff">private </span><span style="color:#2b91af">ISet</span><<span style="color:#2b91af">Answer</span>> m_answers; <span style="color:#0000ff">public </span><span style="color:#2b91af">ISet</span><<span style="color:#2b91af">Answer</span>> Answers { <span style="color:#0000ff">get </span>{ <span style="color:#0000ff">if </span>(<span style="color:#0000ff">this</span>.m_answers == <span style="color:#0000ff">null</span>) <span style="color:#0000ff">this</span>.m_answers = <span style="color:#0000ff">new </span><span style="color:#2b91af">HashedSet</span><<span style="color:#2b91af">Answer</span>>(); <span style="color:#0000ff">return this</span>.m_answers; } <span style="color:#0000ff">private set </span>{ <span style="color:#0000ff">this</span>.m_answers = <span style="color:#0000ff">value</span>; } } } <span style="color:#0000ff">public class </span><span style="color:#2b91af">Answer </span>{ <span style="color:#0000ff">public virtual int </span>AnswerID { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; } <span style="color:#0000ff">public virtual string </span>Name { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; } <span style="color:#0000ff">public virtual </span><span style="color:#2b91af">Question </span>Question { <span style="color:#0000ff">get</span>; <span style="color:#0000ff">set</span>; } }</span></span>

于是这里就有个问题:为什么Answers属性需要同时读写?有的朋友可能会说,NHibernate支持对私有变量的直接读写,这样就可以对外暴露出只读的属性了。这个说法的确没错(而且我已经在这里使用private set了),不过这并不是我这里不满意的地方。更准确的说,我的质疑是“为什么NHibernate会需要设置整个集合容器”?试想一下,在平时的开发中,我们的操作都是向一个集合中添加/删除对象,而不会傻傻地修改对象的集合属性。因为这个集合是对象自己维护的,而不是交给外界去“一锅端”地设置。

可以设置的容器属性并不仅仅是“感官”上的问题。假如,我使用了上面代码,那么我在向数据库插入数据时可能就是这样做的:

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">var </span>question = <span style="color:#0000ff">new </span><span style="color:#2b91af">Question</span>(); question.Answers.Add(<span style="color:#0000ff">new </span><span style="color:#2b91af">Answer </span>{ Name = <span style="color:#a31515">"Answer 1"</span>, <span style="color:#ff0000">Question = question </span>}); question.Answers.Add(<span style="color:#0000ff">new </span><span style="color:#2b91af">Answer </span>{ Name = <span style="color:#a31515">"Answer 2"</span>, <span style="color:#ff0000">Question = question </span>}); <span style="color:#008000">// put it into session</span></span></span>

看看这两句红色的代码是不是有些多余?不仅仅是多余,这儿的问题在于,如果可以这样自由设置Question属性的话,那么我们是不是也有可能“一不小心”造成Answer与所在Question不匹配的问题呢?仅仅是创建还好,如果在一个场景下需要同时操作两个Question或Answer,它们的关系可能就复杂了。NHibernate就是这样,它需要我们手动地维护Question和Answer的双向引用,否则插入/删除/更新都可能不正确。

有些人的解决方法是添加额外的方法,例如AddAnswer:

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">public class </span><span style="color:#2b91af">Question </span>{ ... <span style="color:#0000ff">public void </span>AddAnswer(<span style="color:#2b91af">Answer </span>answer) { <span style="color:#0000ff">if </span>(answer.Question != <span style="color:#0000ff">null</span>) { answer.Question.Answers.Remove(answer); } answer.Question = <span style="color:#0000ff">this</span>; <span style="color:#0000ff">this</span>.Answers.Add(answer); } }</span></span>

使用AddAnswer方法便可以自动地剥离Answer与原有Question的关系,并且与新的Question建立联系了。同理,从一个Question对象中删除一个Answer对象,或者修改Answer对象的Question属性,应该都会引起双方关系的变化。但是,即便我们提供了完整的关系维护手段,Question.Answers还是对外暴露,开发人员还是可以修改Answers集合。

因此,最好的办法其实应该是在集合中提供一种维护关系的方式。例如LINQ to SQL在这一点上便做的不错:

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">public partial class </span><span style="color:#2b91af">Question</span> { <span style="color:#0000ff">private static </span><span style="color:#2b91af">PropertyChangingEventArgs </span>emptyChangingEventArgs = <span style="color:#0000ff">new </span><span style="color:#2b91af">PropertyChangingEventArgs</span>(<span style="color:#2b91af">String</span>.Empty); <span style="color:#0000ff">private int </span>_QuestionID; <span style="color:#0000ff">private string </span>_Name; <span style="color:#0000ff">private </span><span style="color:#2b91af">EntitySet</span><<span style="color:#2b91af">Answer</span>> _Answers; <span style="color:#0000ff"> public </span>Question() { <span style="color:#0000ff">this</span>._Answers = <span style="color:#0000ff">new </span><span style="color:#2b91af">EntitySet</span><<span style="color:#2b91af">Answer</span>>( <span style="color:#0000ff">new </span><span style="color:#2b91af">Action</span><<span style="color:#2b91af">Answer</span>>(<span style="color:#0000ff">this</span>.attach_Answers), <span style="color:#0000ff">new </span><span style="color:#2b91af">Action</span><<span style="color:#2b91af">Answer</span>>(<span style="color:#0000ff">this</span>.detach_Answers)); } <span style="color:#0000ff">public int </span>QuestionID { ... } <span style="color:#0000ff">public string </span>Name { ... } <span style="color:#0000ff">public </span><span style="color:#2b91af">EntitySet</span><<span style="color:#2b91af">Answer</span>> Answers { <span style="color:#0000ff">get </span>{ <span style="color:#0000ff">return this</span>._Answers; } <span style="color:#0000ff">set </span>{ <span style="color:#0000ff">this</span>._Answers.Assign(<span style="color:#0000ff">value</span>); } } <span style="color:#0000ff">private void </span>attach_Answers(<span style="color:#2b91af">Answer </span>entity) { entity.Question = <span style="color:#0000ff">this</span>; } <span style="color:#0000ff">private void </span>detach_Answers(<span style="color:#2b91af">Answer </span>entity) { entity.Question = <span style="color:#0000ff">null</span>; } }</span></span>

看看LINQ to SQL对我们多体贴,自动生成的代码会帮我们维护Question与Answer之间的双向关系。当然,还有一部分逻辑是在Answer类的Question属性中,如果您感兴趣可以自己去观察一下。不过,LINQ to SQL的问题在于它使用了特殊的类型EntitySet,它会使用两个回调函数对外公布集合内元素的添加/删除情况。按理来说,如果我们想要在NHibernate中采用这种“自动维护”的方式,可以使用自定义的集合类型,例如:

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">private </span><span style="color:#2b91af">ISet</span><<span style="color:#2b91af">Answer</span>> m_answers; <span style="color:#0000ff">public </span><span style="color:#2b91af">ISet</span><<span style="color:#2b91af">Answer</span>> Answers { <span style="color:#0000ff">get </span>{ <span style="color:#0000ff">if </span>(<span style="color:#0000ff">this</span>.m_answers == <span style="color:#0000ff">null</span>) <span style="color:#0000ff">this</span>.m_answers = <span style="color:#0000ff">new </span><span style="color:#2b91af">CallbackSet</span><<span style="color:#2b91af">Answer</span>>(...); <span style="color:#0000ff">return this</span>.m_answers; } <span style="color:#0000ff">private set </span>{ <span style="color:#0000ff">this</span>.m_answers = <span style="color:#0000ff">value</span>; } }</span></span>

只可惜,在新建对象的时候我们自然利用到CallbackSet<Answer>,其中包含了我们定义的逻辑。但是如果是这样的代码呢?

<span style="color:#333333"><span style="background-color:#ffffff"><span style="color:#0000ff">var </span>question = session.Get<<span style="color:#2b91af">Question</span>>(1); question.Answers.Add(<span style="color:#0000ff">new </span><span style="color:#2b91af">Answer </span>{ Name = <span style="color:#a31515">"Answer 1"</span>, Question = question }); question.Answers.Add(<span style="color:#0000ff">new </span><span style="color:#2b91af">Answer </span>{ Name = <span style="color:#a31515">"Answer 2"</span>, Question = question }); session.Flush();</span></span>

在从数据库中获取Question对象的时候,NHibernate便会“自作主张”地将Answers属性“整个”设为自己的ISet<Answer>对象——因为实现延迟加载,它也并不一定是HashedSet<Answer>。换句话说,NHibernate虽然能够保持属性的逻辑,但它不能保持自定义集合的逻辑。在我看来,NHibernate完全可以做到放弃集合属性的set操作,把所有的对象都通过集合的Add方法添加进去。其实这样做同样可以实现集合的延迟加载,就好比放弃对所有方法的强制virtual要求,也能实现对象的延迟加载一样。

为了避免像上次那样误解NHibernate,我刚才又作了一次测试——这次我应该没有搞错。当然,如果NHibernate支持对自定义集合类型那就再好不过了,我们就有办法解决这个问题。但是我不知道该怎么做,如果您知道的话,请告诉我。在我看来,目前的问题是NHibernate对于POCO支持有缺陷造成的。如果是这样的话,那我们的Model就不得不继续迁就NHibernate了。

关于NHibernate集合还有一个有趣的问题是——请关注上面这4行代码(Get-Add-Flush这段),这是一个非常标准也是非常常见的添加Answer对象的方式。只可惜,在调用ISet<Answer>的Add方法添加Answer对象的时候,会引发一次数据库查询操作,加载当前Question下的所有Answer——但是在我看来这根本没有必要啊。我只是“添加”,并没有要查询。其实NHibernate帮我把新的Answer对象保存起来就可以了,为什么要增加无畏的开销呢?当然我承认,这个做法会产生一些麻烦,例如需要将集合的操作分为“读”和“写”两类,当“写”操作发生时不会加载数据,而只有在第一次“读”的时候才去数据库查询。“读”和“写”分离,本来就应该这样。

那么谁又做到这一点了呢?又是LINQ to SQL。其实LINQ to SQL在细节上有非常多的考虑,使用起来也是非常容易的——如果我不是被它“宠坏”的话,可能也就不会在意NHiberante的这个问题了。

只可惜,对于ORM的生命“映射方式”上,LINQ to SQL的支持过于有限,这也大大限制了项目对它的接受程度。

相关新闻

  • 立创EDA 原理图转PCB实战:3步完成转换并解决5类封装错误
  • 多相机画面割裂根治方案:MatrixFusion融合引擎核心原理详解
  • 三十多个 AI Agent,谁已经凉了

最新新闻

  • 2149567-00-8 PSMA binder-2 特性
  • GHelper:为华硕笔记本用户量身打造的轻量级硬件控制解决方案
  • 基于本地AI工具构建高效学习系统:从药学备考到通用学习流程自动化
  • 三星固件下载终极指南:Bifrost如何让官方固件获取变得简单快速
  • 如何利用Arsenal-Image-Mounter实现突破性磁盘镜像挂载:一站式专业指南
  • 2026年6月文章一览

日新闻

  • AI智能体安全防护框架AgentGuard:从原理到实战部署指南
  • KMX63与PIC18F26K40硬件组合及低功耗设计实践
  • 基于YOLO13改进的门体检测模型:C3k2模块与PoolingFormer技术解析

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号