我对NHibernate的感受(3):有些尴尬的集合支持
既然是一个ORM框架,那么自然是将O这一端映射R上。至于集合,是O这方面最常见,也是R这一边非常容易表示的关系。例如,一个问题(Question)可以包含多个回答(Answer),于是我的代码里就有这样的结构:
public class Question
{
public virtual int QuestionID { get; set; } public virtual string Name { get; set; } private ISet<Answer> m_answers;
public ISet<Answer> Answers
{
get
{
if (this.m_answers == null)
this.m_answers = new HashedSet<Answer>(); return this.m_answers;
}
private 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; }
}
于是这里就有个问题:为什么Answers属性需要同时读写?有的朋友可能会说,NHibernate支持对私有变量的直接读写,这样就可以对外暴露出只读的属性了。这个说法的确没错(而且我已经在这里使用private set了),不过这并不是我这里不满意的地方。更准确的说,我的质疑是“为什么NHibernate会需要设置整个集合容器”?试想一下,在平时的开发中,我们的操作都是向一个集合中添加/删除对象,而不会傻傻地修改对象的集合属性。因为这个集合是对象自己维护的,而不是交给外界去“一锅端”地设置。
可以设置的容器属性并不仅仅是“感官”上的问题。假如,我使用了上面代码,那么我在向数据库插入数据时可能就是这样做的:
var question = new Question();
question.Answers.Add(new Answer { Name = "Answer 1", Question = question });
question.Answers.Add(new Answer { Name = "Answer 2", Question = question }); // put it into session
看看这两句红色的代码是不是有些多余?不仅仅是多余,这儿的问题在于,如果可以这样自由设置Question属性的话,那么我们是不是也有可能“一不小心”造成Answer与所在Question不匹配的问题呢?仅仅是创建还好,如果在一个场景下需要同时操作两个Question或Answer,它们的关系可能就复杂了。NHibernate就是这样,它需要我们手动地维护Question和Answer的双向引用,否则插入/删除/更新都可能不正确。
有些人的解决方法是添加额外的方法,例如AddAnswer:
public class Question
{
... public void AddAnswer(Answer answer)
{
if (answer.Question != null)
{
answer.Question.Answers.Remove(answer);
} answer.Question = this;
this.Answers.Add(answer);
}
}
使用AddAnswer方法便可以自动地剥离Answer与原有Question的关系,并且与新的Question建立联系了。同理,从一个Question对象中删除一个Answer对象,或者修改Answer对象的Question属性,应该都会引起双方关系的变化。但是,即便我们提供了完整的关系维护手段,Question.Answers还是对外暴露,开发人员还是可以修改Answers集合。
因此,最好的办法其实应该是在集合中提供一种维护关系的方式。例如LINQ to SQL在这一点上便做的不错:
public partial class Question
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); private int _QuestionID; private string _Name; private EntitySet<Answer> _Answers; public Question()
{
this._Answers = new EntitySet<Answer>(
new Action<Answer>(this.attach_Answers),
new Action<Answer>(this.detach_Answers));
} public int QuestionID { ... } public string Name { ... } public EntitySet<Answer> Answers
{
get
{
return this._Answers;
}
set
{
this._Answers.Assign(value);
}
} private void attach_Answers(Answer entity)
{
entity.Question = this;
} private void detach_Answers(Answer entity)
{
entity.Question = null;
}
}
看看LINQ to SQL对我们多体贴,自动生成的代码会帮我们维护Question与Answer之间的双向关系。当然,还有一部分逻辑是在Answer类的Question属性中,如果您感兴趣可以自己去观察一下。不过,LINQ to SQL的问题在于它使用了特殊的类型EntitySet,它会使用两个回调函数对外公布集合内元素的添加/删除情况。按理来说,如果我们想要在NHibernate中采用这种“自动维护”的方式,可以使用自定义的集合类型,例如:
private ISet<Answer> m_answers;
public ISet<Answer> Answers
{
get
{
if (this.m_answers == null)
this.m_answers = new CallbackSet<Answer>(...); return this.m_answers;
}
private set
{
this.m_answers = value;
}
}
只可惜,在新建对象的时候我们自然利用到CallbackSet<Answer>,其中包含了我们定义的逻辑。但是如果是这样的代码呢?
var question = session.Get<Question>(1);
question.Answers.Add(new Answer { Name = "Answer 1", Question = question });
question.Answers.Add(new Answer { Name = "Answer 2", Question = question });
session.Flush();
在从数据库中获取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的支持过于有限,这也大大限制了项目对它的接受程度。
转自:http://blog.zhaojie.me/2009/10/my-view-of-nhibernate-3-collection-support.html
我对NHibernate的感受(3):有些尴尬的集合支持的更多相关文章
- 我对NHibernate的感受(4):令人欣喜的Interceptor机制
之前谈了NHibernate的几个方面,似乎抱怨的居多,不过这次我想谈一下我对Interceptor的感受,则基本上都是好话了.这并不一定是说Interceptor设计的又多么好(事实上它使用起来还是 ...
- 我对NHibernate的感受(2):何必到处都virtual
上一篇主要是在夸NHibernate实现的好,而这篇就完全是来抱怨的了.NHiberante有个毛病,就是如果是和数据库产生映射的类,就要求所有的public成员和protected成员必须是virt ...
- 我对NHibernate的感受(1):对延迟加载方式的误解
NHibernate是.NET平台上最著名的ORM框架,虽说出身于Java平台上的Hibernate,但是从外部看来这几乎就是一个.NET平台上的原生产品:有自己的社区,有自己的用户,有自己的商业支持 ...
- 在 NHibernate 中一切必须是 Virtual 的吗?
原文地址:Must Everything Be Virtual With NHibernate? 老赵在博文中 我对NHibernate的感受(2):何必到处都virtual 提到这篇文章,顺便翻译一 ...
- 我的NHibernate曲折之行
之前,看过很多NHibernate的东西.特别是 YJingLee的NHibernate之旅系列比较经典.看得多了,但是还没有真正的从头到尾的做过一边.今天从头到尾做了一遍,发现问题还真多.我就将我做 ...
- [NHibernate]集合类(Collections)映射
系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...
- NHibernate系列文章二十七:NHibernate Mapping之Fluent Mapping基础(附程序下载)
摘要 从这一节起,介绍NHibernate Mapping的内容.前面文章都是使用的NHibernate XML Mapping.NHibernate XML Mapping是NHibernate最早 ...
- NHibernate讲解
第1章 NHibernate体系结构 总览 对NHibernate体系结构的非常高层的概览: 这幅图展示了NHibernate使用数据库和配置文件数据来为应用程序提供持久化服务(和持久化的对象). 我 ...
- MyGeneration模板生成NHibernate映射文件和关系(one-to-one,one-to-many,many-to-many)
MyGeneration的几个NHibernate模板功能已经很强,但还是存在些问题.例如:Guid主键支持不佳,代码不易修改,不支持中文注释等等.所以我决定自己来改写此模版.我把一部分通用的函数提取 ...
随机推荐
- Java容器---Set: HashSet & TreeSet & LinkedHashSet
1.Set接口概述 Set 不保存重复的元素(如何判断元素相同呢?).如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象. Set中最常被使用的是测试归属性,你可以 ...
- C++ "multiple definition of .. first defined here"
C++ "multiple definition of .. first defined here" 在C++中,有时候需要在不同文件中使用同一个变量.对于这类变量如果处理不当,很 ...
- innosetup语法详解
; 脚本由 Inno Setup 脚本向导 生成! ; 有关创建 Inno Setup 脚本文件的详细资料请查阅帮助文档! ;Inno Setup 是一个免费的 Windows 安装程序制作软件. ; ...
- HTML5练习2
1.邮箱注册网页 主要代码: <!doctype html> <html> <meta charset="utf-8"> <title&g ...
- kvm图形化管理工具
1丶windows环境下载安装以及运行xming软件 https://xming.en.softonic.com/ 链接:https://pan.baidu.com/s/1wMb2pK4WfCilS8 ...
- 安卓逆向之基于Xposed-ZjDroid脱壳
http://bbs.pediy.com/thread-218798.htm 前言 之前介绍了普通常见的反编译模式 但对于使用了 360加固 棒棒 爱加密 等等的加固应用就没办法了. 你会发现 ...
- CSU训练分类
√√第一部分 基础算法(#10023 除外) 第 1 章 贪心算法 √√#10000 「一本通 1.1 例 1」活动安排 √√#10001 「一本通 1.1 例 2」种树 √√#10002 「一本通 ...
- C#语言-NPOI.dll导入Excel功能的实现
前言:刚工作那会,公司有一套完善的MVC框架体系,每当有导入EXCEL功能要实现的时候,都会借用框架里自带的导入方法,一般三下五除二就完成了,快是快,可总是稀里糊涂的,心里很没有底.前几天,在另一个原 ...
- Python时间日期格式化之time与datetime模块总结
1 引言 在实际开发过程中,我们经常会用到日期或者时间,那么在Python中我们怎么获取时间,以及如何将时间转换为我们需要的格式呢?在之前的开发中,也曾遇到time.datetime等模块下的不同函数 ...
- JAVAEE——Solr:安装及配置、后台管理索引库、 使用SolrJ管理索引库、仿京东的电商搜索案例实现
1 学习回顾 1. Lucene 是Apache开源的全文检索的工具包 创建索引 查询索引 2. 遇到问题? 文件名 及文件内容 顺序扫描法 全文检索 3. 什么是全文检索? 这种先创建索引 再 ...