对象关系映射概念性示例(怀疑ORM者请进)
***引言***
过去我一直怀疑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