对象关系映射概念性示例(怀疑ORM者请进)

bonhomme 2007-05-25 05:07:39
***引言***

过去我一直怀疑ORM,感觉它充其量可以用在小型的、具有简单业务模型和数据关系的项目中。复杂的项目,即使我们用尽了各种复杂的SQL“技巧”,还嫌不够,ORM再聪明,怎么可以生成那么复杂,有时简直是任意复杂的SQL语句呢?

直到有一天,我妻子无意中告诉我:织一件带花样图案的毛衣,其实只需要两根竹钎和一条毛线,织女甚至可以心不在焉地织!我终于恍然大悟,我们用惯了各种复杂SQL语句和DBMS提供的专有技术,是因为我们的业务模型和业务逻辑的设计实在太差,甚至没有设计!我们是被迫用千百根针和千百条线痛苦地编制只有我们自己才能理解的图案。

—— ORM不但适用于小项目,更适用于大项目。越是复杂的项目,受益越明显。


***业务对象模型***

我们将采用一个定单的数据模型。这个数据模型其实很复杂,涉及若干个业务对象(类和相应的数据库表),至少应该有5个:
1. 定单(Orders)
2. 定单明细(Order Details)
3. 厂家(Manufacturers)
4. 客户(Customers)
5. 产品(Products)

这5个业务对象以及它们之间的关系分别用下面5个C#类来大致表示:

[Table(”TBL_MANUFACTURER”)]
public class ManufacturerClass //厂家
{
[Column(”ID”)] public string ID;
[Column(”NAME”)] public string Name;
[Column(”CITY”)] public string City;
[Column(”ADDRESS”)] public string Address;
[Column(”BANK_AACOUNT”)] public string BankAccount;
[Column(”TAX_ACCOUNT”)] public string TaxAccount;
......
}

[Table(”TBL_CUSTOMER”)]
public class CustomerClass // 客户
{
[Column(”ID”)] public string ID;
[Column(”NAME”)] public string Name;
[Column(”CITY”)] public string City;
[Column(”ADDRESS”)] public string Address;
[Column(”BANK_AACOUNT”)] public string BankAccount;
[Column(”TAX_ACCOUNT”)] public string TaxAccount;
......
}

[Table(”TBL_PRODUCT”)]
public class ProductClass // 产品
{
[Column(”ID”)] public string ID;
[Column(”NAME”)] public string Name;
[Column(”PRICE”)]decimal Price;
[RELATION(KeyColumn=”MANUFACTURER_ID”)]
public ManufacturerClass Manufacturer; //厂家
......
}

[Table(”TBL_ORDER”)]
public class OrderClass // 定单
{
[Column(”ID”)] public string ID;
[Column(”DATE”)] datetime string Date;
[Column(”DELIVERY_ADDRESS”)] public string DelieveryAddress;
[Relation(KeyColumn=”CUSTOMER_ID”)] public CustomerClass Customer; //客户
[Relation(KeyColumn=”ORDER_ID”, IsDetail=true)]
public OrderDetailClass[] Details; //定单明细,数组,一对多主从关系
......
}

[Table(”TBL_ORDERDETAIL”)]
public class OrderDetailClass //定单明细
{
[Relation(KeyColumn=”ORDER_ID”)] public OrderClass Order;// 定单对象
[Relation(KeyColumn=”PRODUCT_ID”)] public ProductClass Product; //产品
[Column(“QUANTITY”)] public int Quantity;
[Column(“PRICE”)] public decimal Price;
/////////////////
public decimal ItemTotalPrice {get return (Quantity * Price)} //非映射属性
......
}

我们使用了Table/Column/Relation三个Attributes分别来作表映射、列映射和关系映射的元数据,三个Attribute的语义和语法一目了然,稍微值得注意是对主从关系(OrderClass.Details属性)的映射。

***CRUD操作***

下面展示如何通过ORM对上面的数据模型进行CRUD四项基本操作:

[1] 添加定单:
获得ORM的入口对象
ORMContext ormContext =ORMContext.GetContext(…);

建立定单对象
OrderClass order = new OrderClass();
order.ID = "0001";
order.Date = ......;
order.Customer = ormContext.Load<CustomerClass>("033232"); //通过客户代码获得一个
order.DelieveryAddress = "北京市黄浦区……";
客户对象

建立一个定单明细对象
OrderDetailClass orderItem = new OrderDetailClass()
orderItem.Product = ormContext.Load<ProductClass>("020021"); //通过产品代码获得一个产品对象
orderItem.Quantity = 100;
orderItem.Price = orderItem.Product.Price; //注意这一句
order.Details.Add(orderItem);
建立更多的定单明细对象
.......
保存定单,完成操作
ormContext.Save<OrderClass>(orderItem);

[2] 读取定单
OrderClass order = ormContext.Load("0001");
然后,我们可以访问各种来自上面5个类(表)的信息了,例如:
1) 定单的客户的银行帐号:
string bankAccount = order.Customer.BankAccount;
2) 定单中的第1个产品的厂家的地址
string ManufacturerAddress = order.Details[0].Manufacturer.Address;

[3] 修改定单
先加载一个定单:
OrderClass order = ormContext.Load("0001");
我们要修改定单的发货地址,并且需要删除第六个产品:
order.DelieveryAddress = "北京市朝阳区……";
order.Details.Delete(order.Details[5]);
保存一下,就完成了修改:
ormContext.Save<OrderClass>(orderItem);

[4] 删除定单
一个语句,就把定单和明细全部删除了
ormContext.Delete<OrderClass>(orderItem);


***对象查询***

下面进一步说明如何通过对象查询语言(Object Query Language, OQL)实现典型的二维报表。
例如我们要查询2005年第一季度来自“北京”的客户订购某一编号为“20039023”的厂家的产品的情况。
先定义一个类来组织报表数据集合:
public class OrderReportLine
{
string OrderID, //定单号
datetime Date, //日期
string CustomerName, //客户名称
string CustomerAddress, //客户地址
int Quantity, //数量
decimal Price //单价
}

准备一个OQL查询语句:
string queryText = @”
select Order.ID, Order.Date, Order.Customer.Name,
Order.Customer.Address, Order.Details.Quantity, Order.Details.Price
from Order
where Order.Date >=’20050101’ and Order.Date<’20050401’ and Order.Customer.City = ‘Beijing’ and Order.Details.Product.Manufacturer.ID = ‘20039023’”;

执行查询,获得结果:
OrderReportLine[] orderReport
= ormContext.ExecuteQuery<OrderReportLine>(queryText);

查询结果可以通过数组orderReport进行访问。
注意几点:
1. OQL的基本思想和语法与SQL非常相似,但也有本质区别。OQL的操作对象是类而不是表。from子句后面的Order是c#类名,不是表名。select和where子句里引用的各种名字也是类象名或属性名,不是表名、表别名或列名。另外虽然本例中select子句所列为标量数值,OQL也可以返回整个对象。
2. from子句没有使用join操作,所有的数据都是来自Order对象,和前面CRUD操作 的情形相似,Order类管理了5个业务对象类(表)之间的关系。

注:
文中使用的语法只是概念性的,并不严格符合某种特定ORM工具的具体语法。但各种ORM工具的基本思想都大致和本文所讨论的类似。

本文作者 夏成斌 .NET软件工程师 13764438712


...全文
2242 70 打赏 收藏 转发到动态 举报
写回复
用AI写文章
70 条回复
切换为时间正序
请发表友善的回复…
发表回复
缪军 2011-05-29
  • 打赏
  • 举报
回复
客户,厂商,承运商这些不同的所谓对象,实际上存储的时候,都可能放在org这一个表中,
而功能界面确实各个独立的模块和权限管理;
并且,一个订单业务的来源和目标都有可能是不同的组织机构,

再则,不同的角色对数据的操作方式和输出方式是千差万别的,
比如,应用程序提供的搜索引擎输入"张三",你将要提供关于张三的所有类型的订单索引,然后由用户选择以后再进入精确处理模式,
那种基于CURD和数据驱动思想的方法将会使项目根本经不起需求的风吹草动,更不要说促进用户发展了
  • 打赏
  • 举报
回复
本文和性能有什么关系
michael_jiwei 2008-10-23
  • 打赏
  • 举报
回复
只有一个问题,一般来说,orm映射到数据库都是指定的唯一数据库,如果我有多个数据库再跑,orm貌似不能动态切换数据源的吧?
acqy 2008-10-20
  • 打赏
  • 举报
回复
呵呵,我总结一下,也不知道是否正确
个人认为,ORM的任务就是在业务层“不知道持久化机制是什么、如何工作”的情况下,维持业务对象的“持久化”状态。当业务对象需要持久化时,ORM完成持久化;当业务对象需要从持久化机制中重建时,ORM重建对象。
Hibernate这一词感觉也很有用意:(业务对象的)冬眠,明确说明了持久化这一状态。
当然,我还是坚持具体情况具体分析。不是所有的应用需要ORM。ORM至少会要付出性能代价,是否采用ORM应该要根据应用的规模以及其它的非业务性需求。

cnapc 2008-10-20
  • 打赏
  • 举报
回复
赫赫,谢谢acqy指教.因为每个人的语境不同,那么表达出来意思也会不同,也许是想表述同样的东西.
解耦对于大多数系统来说是必要的
同样对于IDE或语言也是一样,它们也有客户就是开发者,它们同样需要解耦,分层/封装不同的逻辑和实现
如果他们如同偶做出的软件,笨重而又繁琐,大概也不会有多少人使用了...
偶觉得啊,ORM处理的不能够是业务对象,只能是数据对象,如同Java中的实体Bean,这样也许就是你开始说的
NHibernate的轻便快捷?
偶只是将数据对象通过ORM和后台数据作映射(关系也好,存储也好),
而数据之间的存储关系是有穷的,完全可以通过某些表达式来表述.
而逻辑关系应该不会有什么样的东东能够完全涵盖,如果有那一定是AI了
acqy 2008-10-20
  • 打赏
  • 举报
回复
[Quote=引用 62 楼 cnapc 的回复:]
ORM就是工具而已,解耦?解那一层偶?所有的逻辑代码在SQL那一层全部实现,为什么还要到中间层?为什么还要有客户层代码?
不要将某些概念想的太复杂,它们的目的真的很简单呢?
ORM做解耦?代价就太大了。
数据库难道不包括XML数据库?
[/Quote]

我想说明的是,其实数据库概念很广泛,关系型数据库只不过是其中的一种。
你可以选择自己的SQL层而不使用任何ORM,完全取决于你自己。但是无论是你自己写的SQL层,还是ORM,都不得不承担同样一个责任,就是分层解耦。因为业务层是不可能去考虑持久化细节的。
ORM与SQL层还有个细节上的区别,就是O。ORM处理的是业务对象,SQL处理的是数据。你可以去了解一下,.NET中引入Typed Data Set的用意,就能了解到为什么我们需要更多的关注O。
acqy 2008-10-20
  • 打赏
  • 举报
回复
[Quote=引用 62 楼 cnapc 的回复:]
ORM就是工具而已,解耦?解那一层偶?所有的逻辑代码在SQL那一层全部实现,为什么还要到中间层?为什么还要有客户层代码?
不要将某些概念想的太复杂,它们的目的真的很简单呢?
ORM做解耦?代价就太大了。
数据库难道不包括XML数据库?
[/Quote]

我想说明的是,其实数据库概念很广泛,关系型数据库只不过是其中的一种。
你可以选择自己的SQL层而不使用任何ORM,完全取决于你自己。但是无论是你自己写的SQL层,还是ORM,都不得不承担同样一个责任,就是分层解耦。因为业务层是不可能去考虑持久化细节的。
ORM与SQL层还有个细节上的区别,就是O。ORM处理的是业务对象,SQL处理的是数据。你可以去了解一下,.NET中引入Typed Data Set的用意,就能了解到为什么我们需要更多的关注O。
acqy 2008-10-19
  • 打赏
  • 举报
回复
[Quote=引用 60 楼 cnapc 的回复:]
ORM和数据库细节并没有多大关系啊,他只是利于开发人员处理数据时简单灵活些(或者是少写N行代码)
偶们没必要认为ORM就是DB的Administrator,该划分到DB层的实现,还是划分到DB层.
Table的结构/View/存储过程,本来就是DB和应用的接口
如果不使用ORM,现在的代码还不是一样要关联到数据库的细节?
如果偶们认为ORM是一个编程工具(的规范),是不是会好理解些?
[/Quote]

ORM的引入正是为了在业务层(传统三层结构中的业务层)中屏蔽数据库(或者说是外部存储机制)的访问细节,使得业务层能够更加专注在解决业务问题上,而不是访问数据库上。相对较差的设计是,在业务层布满了繁杂的SQL或者存储过程的调用,此时当你的后台数据库有变更的时候,你将会被这一大堆SQL所困扰。在此,说相对较差的意思是,我们需要具体情况具体分析,加入系统本身就不大,那你引入ORM也没必要。如果系统对性能要求很高,那么还不如直接使用ADO。
从效果上来看,ORM确实是让我们少写许多代码。但ORM绝不仅仅是让我们少写些代码这样“肤浅”。这正如MVP模式一样,它使得应用系统的某个层能够更加独立(解耦)于其它层,这样才能使得应用系统更具备扩展性。ORM解决的是状态持久化问题,数据库是目前最为通用的数据保存机制,但对于对象状态的持久化,它不是唯一的机制。比如,对象完全可以在被序列化以后保存到XML文件中。
cnapc 2008-10-19
  • 打赏
  • 举报
回复
ORM和数据库细节并没有多大关系啊,他只是利于开发人员处理数据时简单灵活些(或者是少写N行代码)
偶们没必要认为ORM就是DB的Administrator,该划分到DB层的实现,还是划分到DB层.
Table的结构/View/存储过程,本来就是DB和应用的接口
如果不使用ORM,现在的代码还不是一样要关联到数据库的细节?
如果偶们认为ORM是一个编程工具(的规范),是不是会好理解些?
cnapc 2008-10-19
  • 打赏
  • 举报
回复
补充,并不是说acqy说的不正确^-^
而是各人的表述概念可能有不一样,ORM的确是封装了数据后台
而它对于偶来说也的确是一个工具,不用自己封装不同的数据SQL代码,不用写烦人的某些很相像的代码...
事实上,偶自己做了个类似于Java中Persist概念的库,来处理这些问题
所以从俩个角度来说,全正确
cnapc 2008-10-19
  • 打赏
  • 举报
回复
ORM就是工具而已,解耦?解那一层偶?所有的逻辑代码在SQL那一层全部实现,为什么还要到中间层?为什么还要有客户层代码?
不要将某些概念想的太复杂,它们的目的真的很简单呢?
ORM做解耦?代价就太大了。
数据库难道不包括XML数据库?
丛晓男 2008-10-18
  • 打赏
  • 举报
回复
学习
acqy 2008-10-18
  • 打赏
  • 举报
回复
在ORM上使用Attribute,本身就默认了领域对象需要关联数据库细节,事实上这种做法并不合理。
从分层角度考虑,领域层不应该考虑存储细节问题。在领域对象上设置“Table”、“PrimaryKey”等特性,事实上已经违背了这样的规律。
有兴趣的读者可以去比较一下Castle ActiveRecord以及NHibernate,虽然CA是建立在NHibernate之上,但意思完全不一样。
当然,也不是说NHibernate才是正道,如果项目规模不大,领域层对象关系不复杂的情况下,采用类似于Castle ActiveRecord这样的ORM或许更加轻便快捷。
cnapc 2008-10-18
  • 打赏
  • 举报
回复
偶自己写了个.net的持久化框架,呵呵.
个人觉得保持一个干净简单的目标软件设计框架是应用ORM的前提,ORM的实现倒不是太复杂的事情
可以相互参考.net和Java的ORM实现,有助于理解ORM的构造是为什么
chxljtt 2008-10-14
  • 打赏
  • 举报
回复
以后認真學習
yqyqyoyo 2008-10-08
  • 打赏
  • 举报
回复
mark!
cooolchen 2008-10-02
  • 打赏
  • 举报
回复
好,学习一下
hanjoe109 2008-09-29
  • 打赏
  • 举报
回复
寫得真好,我得好好學學
abcn 2008-09-28
  • 打赏
  • 举报
回复
mark
makecodeeasy 2008-09-13
  • 打赏
  • 举报
回复
mark
加载更多回复(50)

13,190

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 分析与设计
社区管理员
  • 分析与设计社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧