一、症状
老谭的角色之一是老中医,在电线杆子上贴小广告专治疑难杂症那种。
有一个问题袁俊杰已经反映两次了,就是在数据保存过程中NHibernate总是要更新Meter类的对象,而Meter类是从视图映射过来的,视图中定义了两个派生列,因此是不能更新的。问题是这个过程中从未对Meter对象的属性做过任何修改,而且所有引用Meter对象的关联关系中,cascade属性均置为none,update属性置为false(这个设置就属于病急乱投医了)。
问题到底出在哪儿呢?
二、原因
在项目的代码中找不到原因,老谭只得下狠手了:将各方的源代码找来,包括项目的,平台的,Spring.NET的,NHibernate的,将它们集中到一个Solution中,跟踪NHibernate的源代码。
结果发现,原因在于,NHibernate想要自动修补空属性的缺省值。
Meter类中有一个属性:
public class Meter
{
...
/// <summary>))
/// 是否为虚表
/// </summary>
[Property(Column = "real_flag")]
public virtual bool IsVirtual { get; set; }
...
}
其对应的数据表中的字段为real_flag:
注意real_flag这个字段是可空的,而Meter中IsVirtual属性为非空的。
在数据库中,当前访问记录的real_flag字段的值正是空值:
在这种情况下,NHibernate就做了一件“好事”:将空值null转换为bool型的缺省值false,将其赋给Meter的属性isVirtual。对Meter对象的这种修改,会回写到数据库中。我们称之为“自动修补空属性的缺省值”。
问题是,Meter对应的数据库对象是一个不可更新的视图,这最终导致NHibernate的异常。
总结一下,问题的根本原因在于Hibernate映射中属性的失配:表中的字段是可空的,而类中的属性是非空的。
三、解决
找到了问题的原因,解决起来就轻松了。下面任一方法都可以解决映射失配问题:
- 不修改数据库,而将类的属性设为可空的;
- 不修改程序代码,而将表的字段设为非空的;
- 在保存数据库记录时,确保给real_flag字段赋非空的值(注意:为该字段设置缺省值不能完全地解决该问题)。
四、启示
- 开源代码有其潜在的优势。如果成熟水平相当,优先考虑开源代码。
- 设法找到问题症结所在,通过调整使用方式解决问题。不宜绕开或放弃NHibernate,如利用SQL直接操纵数据;也不宜通过修改NHibernate的实现,构建自己专用的NHibernate版本来解决问题。
- 做好架构设计。在架构中,隔离数据访问层、业务逻辑层和展现层的模型设计。特别是数据访问层中,Poco对象承担单一的数据交互职责,避免复杂的映射。