设计是否可以更合理一点?——关于ORM中业务实体的讨论

今天看到David Hayden写的Castle ActiveRecord - Active Record Pattern Built on NHibernate - Rapid Application Development文章,其中他的实体类设计如下:

[ActiveRecord("Posts")]

public class Article : ActiveRecordBase<Post>
{
    
private int _id;

    [PrimaryKey(PrimaryKeyType.Native, 
"PostId")]
    
public int Id
    
{
        
getreturn _id;}

        
set{ _id = value;}
    }


    
private int _blogId;

    [Property]
    
public int BlogId
    
{
        
getreturn _blogId;}

        
set{ _blogId = value;}
    }


    
private int _categoryId;

    [Property]
    
public int CategoryId
    
{
        
getreturn _categoryId;}

        
set{ _categoryId = value;}
    }


    
private string _title = string.Empty;

    [Property]
    
public string Title
    
{
        
getreturn _title;}

        
set{ _title = value;}
    }


    
private string _description = string.Empty;

    [Property]
    
public string Description
    
{
        
getreturn _description;}

        
set{ _description = value;}
    }

}

注意到出现了下面这样的两个属性:

public int BlogId

public int CategoryId

在这个业务实体中,对于Article对象来说,更直观的应该说它属于哪一个Blog,哪一个Category,而不是指定一个整型的值,这种用ID的设计其实是把把数据库结构带入到了业务实体中。我们知道引入ORM,使得我们可以用面向对象的思维来考虑实体间的关系,如果继续使用ID来解决,引入ORM的作用可能就大打折扣了,因此,是否把实体类修改为如下这样更合理一些呢?

[ActiveRecord("Posts")]

public class Article : ActiveRecordBase<Post>
{
    
private int _id;

    [PrimaryKey(PrimaryKeyType.Native, 
"PostId")]
    
public int Id
    
{
        
getreturn _id;}

        
set{ _id = value;}
    }


    
private Blog _blog;

    [Property]
    
public Blog Blog
    
{
        
getreturn _blog;}

        
set{ _blog = value;}
    }


    
private Category _category;

    [Property]
    
public Category Category
    
{
        
getreturn _category;}

        
set{ _category = value;}
    }


    
private string _title = string.Empty;

    [Property]
    
public string Title
    
{
        
getreturn _title;}

        
set{ _title = value;}
    }


    
private string _description = string.Empty;

    [Property]
    
public string Description
    
{
        
getreturn _description;}

        
set{ _description = value;}
    }

}

即用这里的两个属性来代替整型的ID

public Category Category

public Blog Blog

估计也有很多朋友会这样去用,下午跟一个朋友讨论时,他说修改前加载Article对象时,加载的仅仅是2个ID,而修改后却要加载Blog,Category对象所有的属性,是否存在性能上的下降?欢迎大家就这个问题说出你的看法。

(出处:http://terrylee.cnblogs.com

作者:TerryLee
出处:http://terrylee.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2006-06-19 17:59 TerryLee 阅读(3795) 评论(38)  编辑 收藏 所属分类: NHibernateCastle Project

  回复  引用    
#1楼 2006-06-19 20:13 | CHNBin [未注册用户]
个人偏好第一种。
原因:虽然第二种更符合OO思想,但是第二种简单明了。

  回复  引用  查看    
#2楼 2006-06-19 20:47 | 麒麟.NET      
第二种!
性能上的优劣完全可以忽略不计(不是指性能下降不多,而是指这种性能上的牺牲换来的是应对变化时的方便)
BolgId相对于Blog对象哪个是更不易变化的?当然是Blog对象
既然一种设计更符合OO思想,那么我们为什么不用它呢?

  回复  引用  查看    
#3楼 2006-06-19 21:14 | 蔡克伦      
毫无疑问绝对是第二种啊!这个难道到现在还有疑问吗?
性能上有“延迟加载”啊,还有“二级对象的缓存”,这两项功能是hibernate在实际项目中必须使用的。
  回复  引用  查看    
#4楼 [楼主]2006-06-20 07:59 | TerryLee      
@蔡克伦
@麒麟.NET
同感!
  回复  引用  查看    
#5楼 [楼主]2006-06-20 08:00 | TerryLee      
@CHNBin
没看明白:-)
“个人偏好第一种,虽然第二种更符合OO思想,但是第二种简单明了”
  回复  引用  查看    
#6楼 2006-06-20 08:28 | Henry Liang      
我也觉得应该用第二种。
因为在第一种模式中加载了两个ID之后,相当于和Blog,Category还是没有任何关系——需要Blog和Category的内容的时候,依然需要使用相应的ID来Load——那样还有什么意义呢?还不如直接用store procedure得了。
在性能上的损失也应该不会很大。第一,我们可以使用Lazy Load,第二,从实际使用的角度来讲,一篇文章和一个Blog以及一个Category的关系通常不会改变,因此使用Cache可以在最大程度上消除性能上的损失。
  回复  引用  查看    
#7楼 2006-06-20 09:09 | brightheroes      
如果简单的第一种做法,还是基于DB的R->O的开发模式,这种做法不是OO罢
第二种确实是效率上肯定比第一种差一点,即使是通过某种相应的Lazy读取或者是Cache。
而且如果一个对象特别复杂的话,可能复杂属性相对很多,性能下降是不可避免的,即使是用了这样那样的方式。
但是我们有的时候更关心的是程序的可维护性,而不是计较这么一点资源。
BTW,我下载了Nhibernate的源码,编译通不过,汗……,少了个把文件……。



  回复  引用  查看    
#8楼 2006-06-20 09:18 | yzx110      
@蔡克伦
从OO设计上来说,那肯定是第二种。

不过你说到Lazy Load,我就想起我一个同学用(Hibernate),他说用Lazy Load的方法获取页面上的数据,但是就是因为需要Lazy Load,导致数据库连接长期占用,他说Java里面的那些连接池组件也都有或多或少的问题,导致网站支持的并发访问很少。

这是他遇到的。

不知道你们有没有遇到,怎么解决?或者怎么避免?
  回复  引用  查看    
#9楼 2006-06-20 09:48 | L2      
类似例子中Blog Category这种典型的多对一关系
实际应用中对这种枚举表字段需要进行级联更新的情况很少

俺一般在entity类中同时写

[Property]
public int BlogId
{
get{ return _blogId;}

set{ _blogId = value;}
}

private int _categoryId;

[Property]
public int CategoryId
{
get{ return _categoryId;}

set{ _categoryId = value;}
}

[Property]
public Blog Blog
{
get{ return _blog;}

}

private Category _category;

[Property]
public Category Category
{
get{ return _category;}

}


hbm文件的
<property name="CategoryId" />
<property name="BlogId" />
不动

<many-to-one name="Category" column="CategoryId" insert="false" update="false" />

仅作为加载
可惜nhibernate 不支持属性lazy-load


  回复  引用  查看    
#10楼 2006-06-20 10:44 | hoverfly      
后来他解释了,他这么做只是为了让例子显得简单:)
  回复  引用  查看    
#13楼 [楼主]2006-06-20 11:10 | TerryLee      
@hoverfly
谢谢:-)

这个问题值得初用ORM的朋友注意,不小心有可能就会按照第一种用法去用了。
  回复  引用  查看    
#14楼 2006-06-20 12:45 | henry      
问个业务实体的问题
业务实体暴露在业务规则中是合理?这东西在设计时非常头痛,很多情况下为了方便直接把业务实体中暴露在业务规则中了;但这样我感觉很不合理,业务实体在很多情况并不只是为一个业务规则服务,一旦业务实体暴露在业务规则中就会导致业务规则有歧义.
不知道各位有什么看法?
  回复  引用  查看    
#15楼 2006-06-20 13:51 | SHY520      
个人比较倾向于第二中设计方案,虽然相比于第一种设计方案性能上可能有所降低,但是第二种方法在使用的时候却很方便
  回复  引用    
#16楼 2006-06-20 13:54 | _greatqn [未注册用户]
我们是俩个都放。
有BlogId,也有Blog对象。

  回复  引用  查看    
#17楼 2006-06-20 13:58 | L2      
@henry
如果这里的实体只是POJO不是ar模式
个人认为不仅在业务层暴露没问题
应该划到Common里让各层饮用都可以

参见duwamish
  回复  引用  查看    
#18楼 2006-06-20 14:30 | gozh2002      
IMHO, the first solution will unavoidable lead to some service/manager/dao class to write business rules, then leads to data procedure method design, it is not OO.

the second soultion will be posssible to make it as POJO, with the underline business rules inside the object, which is a pure oo design.

Does this actually mean AR pattern is not really an OO design?

But I still doubt how much do we really need OO for most of database application.








  回复  引用  查看    
#19楼 2006-06-20 16:02 | hoverfly      
@gozh2002
Does this actually mean AR pattern is not really an OO design?
何出此言?
@TerryLee
用DiscriminatorColumn来把多个实体持久化到一个表里,必须把父类定义成ActiveRecord吗?我试验没有做定义,结果出了一些奇怪的问题。
  回复  引用  查看    
#20楼 2006-06-20 16:02 | 维生素C.NET      
个人观点,虽然第二种看起来比较适合OO的思想,但是有一点,作为database driven的程序,这样做在R-O的时候是不是还是比较牵强?
  回复  引用  查看    
#21楼 [楼主]2006-06-20 18:10 | TerryLee      
@hoverfly
是的,试验出什么问题了?
  回复  引用  查看    
#22楼 [楼主]2006-06-20 18:12 | TerryLee      
@维生素C.NET

我不这么认为,能否解释一下呢?
  回复  引用  查看    
#23楼 2006-06-20 19:57 | hoverfly      
@TerryLee
因为父类使用了泛型,如果作为ActiveRecord的话会出现错误,所以只好只把子类定义为ActiveRecord。保存的时候一切正常,但是查找的时候发现DiscriminatorColumn没有起作用,查看数据库,DiscriminatorColumn是被正确记录了的。
  回复  引用  查看    
#24楼 2006-06-20 21:20 | Zhongkeruanjian      
别这样想。用ID还是用实体是具体的系统决定的(不过大多数系统都是要实体的)

你的
[PrimaryKey(PrimaryKeyType.Native, "PostId")]属性

不是也是数据库的么
  回复  引用  查看    
#25楼 [楼主]2006-06-21 08:06 | TerryLee      
@Zhongkeruanjian
PostID是主键,而其它的两个ID则是外键
  回复  引用  查看    
#26楼 2006-06-21 13:32 | 双鱼座      
@TerryLee
1.好象还没有见到哪个系统采用你所说的第一种方案吧。
2.Lazy-Load的确不是灵丹妙药。我写过类似的文章。http://barton131420.cnblogs.com/archive/2005/08/10/211217.html
3.不过任何人都不用担心性能问题。因为任何情况下,访问引用的相关行是不可避免的,不同的只是或者放到一个数据库连接中访问,或者放到两个数据库连接中访问。
  回复  引用  查看    
#27楼 [楼主]2006-06-21 14:09 | TerryLee      
@双鱼座
在David Hayden的文章中看到他这样写,所以拿出来讨论一下,不过初用ORM很有可能就会写成第一种:-)
  回复  引用  查看    
#28楼 2006-06-21 17:48 | hoverfly      
@双鱼座
有一点想法不太一样,对于有连接池的情况,多次连接比长时间占用应该更有效率。
还有:
举个例子吧。你要加载某段时间所有购买XXX产品的订单项。如果是LazyLoad就只需要加载订单项,但是当需要获取订单项所在订单的日期的时候你不得不去再次启动数据库连接加载这个订单项所在的订单。但是如果你要加载某段时间的所有订单,我不一定关心每个订单所有的订单项。如果需要可以手动或自动加载,但不能叫LazyLoad。
这不是Lazy-Load吗?
抱歉TerryLee,这有点离题:P
  回复  引用  查看    
#29楼 2006-06-21 18:01 | hoverfly      
就拿上面的例子说,一个Blog对象里应该有一个IList Posts成员。而Post下又可以有其他对象。如果是没有Lazy-Load的话,得到一个Blog对象不就会要查询它下面的所有Post以及它们的下层对象吗?这样会多很多查询。
  回复  引用  查看    
#30楼 2006-06-21 21:52 | 双鱼座      
@hoverfly
不太明白你的意思。如果你要加载满足某个条件的所有订单项,我可以加载这些订单项,然后马上加载这些订单项所引用的订单(这是立即加载而不是lazyLoad)。这是引用的情况。
对于组合的情况,如果你要加载满足某个条件的订单,我不一定会立即加载每个订单的订单项集合(这并不需要LazyLoad)。如果需要的话,我会手工加载,但是我会加载全部需要的订单项而不会每个订单的订单项加载一次,这是个策略问题。其实,我也同意并不是因为连接池或者连接的问题,而是在LazyLoad时你有可能将1个SQL可以加载的过程演变成300个SQL。例如如果你有300个订单满足你指定的条件,LazyLoad时,你只能为每个订单加载一次订单项,这样的确需要加载300次,而本来可以用一次查询加载全部的订单项的。
我这里否决LazyLoad是基于这样一个前提:“一对多”的实体,其中的“一”也不是孤立的。
  回复  引用  查看    
#31楼 2006-06-21 23:20 | hoverfly      
@双鱼座
对于组合的情况,如果你要加载满足某个条件的订单,我不一定会立即加载每个订单的订单项集合(这并不需要LazyLoad)
好像有些ORM框架是直接把每个订单项的集合都加载的吧(不使用Lazy-Load时),至少ActiveRecord是这样。我感觉这样可能效率会有问题(所以需要Lazy-Load),但是很符合OO的原则。如你所说的情况,怎么做到一次查询得到所有300个订单的订单项呢,用in查询吗?怎么让找到的订单项和300个订单保持关系呢?
  回复  引用  查看    
#32楼 [楼主]2006-06-22 08:05 | TerryLee      
@hoverfly
呵呵,没什么,讨论问题嘛:-)
  回复  引用  查看    
#33楼 2006-06-26 13:38 | LIVE      
我觉得2种在不同的情况下运用不同,我个人认为第2种比较直观一些,但是在有的时候没有必要,比如说在一些移动设备开发上(或者说是内存受限)的情况下第一种情况可能回更好一些。

还有上面说的延迟加载的问题,其中的一个目的就是解决这样一个问题。
  回复  引用  查看    
#34楼 2006-10-20 11:18 | 奔放      

引用 hoverfly 的话: 就拿上面的例子说,一个Blog对象里应该有一个IList Posts成员。而Post下又可以有其他对象。如果是没有Lazy-Load的话,得到一个Blog对象不就会要查询它下面的所有Post以及它们的下层对象吗?这样会多很多查询。

我还是没太清楚该怎么处理这个问题。
Post实体里有Blog对象
Blog实体里有Posts列表对象(可能是IList)

  回复  引用    
#35楼 2006-11-17 09:22 | hansong [未注册用户]
就拿上面的例子来说, 第2种方式, 我要查询300条Article对象列表显示出来, 其中的Blog和Category对象是否也要针对每个Article做300次的查询, 这样不是大大影响效率吗? (我用NHibernate做的实验确实如此, 不知道我在使用上有没有问题.)
  回复  引用    
#36楼 2006-12-28 00:54 | qcrsoft [未注册用户]
楼主的例子算是个片段,我想知道Create应该怎么去做呢?
Create一条Post记录,必定是要写BlogId这个字段的,是不是要覆写base.Create方法?在这个方法里把_blog.BlogId取出来往库里写?
  回复  引用    
#37楼 2006-12-28 01:34 | qcrsoft [未注册用户]
楼主,你的修正会不会有这样的一个问题:
在表的设计上,article这个表少不了有个blogId字段对吧?
这样的话,我想按blog检索,Article应该提供一个FindByBlog的方法对吧?
它的参数按OO的思想应该是Blog(是blogId也无所谓,这不是重点)
但问题来了
public static Article[] FindByBlog(Blog blog)
{
  return FindAllByProperty("xxx", blog.Id);
}
上面代码里的xxx应该怎么写呢?xxx应该是Article里的一个属性,可是你的修正后的类里只有一个
[Property]
public Blog Blog

这样岂不是不能按Blog检索文章了?
你的系列文章我还没看完,有没有解决之道?

  回复  引用    
#38楼 2006-12-28 08:43 | qcrsoft [未注册用户]
楼主及众家兄弟啊,为什么我在程序里加入了
[Property]
public qcrsoft.Blog Blog
。。。。。
后会提示
You can't use [Property] on Article.Blog because qcrsoft.Blog is an active record class, did you mean to use BelongTo?
啥意思捏?

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)