Dev 出品的XPO是一个O/R Mapping框架,虽然是商业软件,非开源,但提供了源码。况且Dev的产品一向以精品为主,值得好好研究一下(我不是Dev的代理 )。
于是在学习过程中做个摘要。
一、一个持久类(Persistent Class)一般来说映射为一个表类型,该表的每一条纪录即一个持久类的实例。
持久类继承自XPObject或者XPBaseObject。
public class Customer : XPObject
创建对象就创建了一条新纪录,调用基类的save就保存到库。
XPObject继承自XPBaseObject,已包含Oid属性表示表的唯一自增量字段。
而XPBaseObject需要手动去作一些映射工作,但提供更大的灵活性。
XPBaseObject 实现了IXPObject接口,实际上假如我们自己去实现这个接口,也能自动保存到数据库, 也就是对象持久化了,比如窗体winform的状态就可以保存。
检索一个表,实际就是检索一个同类对象的集合 XPCollection
二、表间关系:
支持一对一,一对多,多对多三种关系。
数据库的表间关系在框架内体现为持久对象之间的关系。一般我们在设计一些类似关系的类时,我们用数组或者其他集合表示方法IList等等,来为类之间建立关系。
XPO使用XPCollection来表示类之间的”对多关系”。并且附加属性来表示多对多还是一对多的关系。
一对多:
在一个类中定义:
public class Customer : XPObject {
...
[Association("CustomerAddresses", typeof(Address))]
public XPCollection Addresses { get { return GetCollection("Addresses"); } }
...
}
同时另一个类Adresses中定义:
[Association("CustomerAddresses")]
public Customer Customer;
此时这个类的关联关系并没有指定关联类型,因为已经说明了类型。
可以在关联关系中进一步指定其他附加属性,比如级联删除关系:
public class Customer : XPObject {
...
[Association("CustomerOrders", typeof(Order)), Aggregated]
public XPCollection Orders { get { return GetCollection("Orders"); } }
...
}
多了Aggregated这种特性,表示聚集,也就是表之间的级联删除关系。
访问子表的方法:
枚举
foreach(Order theOrder in theCustomer.Orders)
增加子表的记录:
myCustomer.Orders.Add(myOrder);
三、查询:
使用条件对象:
比如: Freight <
XPCollection orders = new XPCollection(typeof(Order), new BinaryOperator("Freight",
这里使用了指定持久类类型的集合类XPCollection对象。这里并不意味着只能在一个表中查。如果这 个持久类和其他类有关联关系的话,那么条件对象可以包含对关联类的属性条件。也就是实现了多表查询。下面的组合查询中的Address就是Customer的一个聚集类。
复杂一点的条件使用GroupOperator。比如:BirthDate <
GroupOperator criteria = new GroupOperator();
criteria.Operands.Add(new BinaryOperator("BirthDate", new DateTime(1960, 1, 2), BinaryOperatorType.Less));
criteria.Operands.Add(new BinaryOperator("Address.Street", "10'
... new XPCollection(typeof(Customer), criteria)
以前也打算在项目中做一个类似此功能的组件,当时的出发点是想做一个和具体数据库sql语法无关的条件对象,用来代理sql条件语句的生成。后来由于项目成本原因作罢。现在终于有一个现成的了。
四、继承和多态:
某种程度上也可以说是表间关系。
XPO的持久类支持完整的继承和多态。
比如管理人员是一个员工,但一个员工不一定是管理人员。这个我们在以前数据库设计时可以在员工表中加一个是否管理人员的标识,或者另建一个管理人员表,再通过外键让它和员工表建立关系。
XPO中就可以用OO的方式来描述此类关系:建一个员工类,再建一个员工类的子类:管理人员类。
public class Employee : XPObject {
public string LastName = "";
public string FirstName = "";
[Association("ManagerEmployees")]
public Manager Manager = null;
public Employee() {}
public Employee(string newLastName, string newFirstName) {
LastName = newLastName;
FirstName = newFirstName;
}
}
public class Manager : Employee
{
[Association("ManagerEmployees", typeof(Employee))]
public XPCollection Employees {
get { return GetCollection("Employees"); }
}
public Manager() {}
public Manager(string newLastName, string newFirstName) : base(newLastName, newFirstName) {}
}
注意使用了关联属性。但是这里的关联关系仅仅是表示一个管理人员所管理的下属员工(管理人员)。
五、Session :
管理数据库的连接信息。有一个默认的连接:MS Access OLEDB provider。如果使用它,在程序中就不必自己初始化Session的实例。但如果想使用自己的连接,两种办法:一是更改默认连接的连接信息,二是自己创建,但在持久类构建时必须引用它。还是第一种简单一点。除非应用程序要考虑连接两个数据库
六、对二进制大对象字段的存取
使用延迟装载(Delayed Loading):
这里必须指定一个私有的XPDelayedProperty类型的属性,并且加上Attribute,设计上有点繁琐。
public class Customer: XPObject {
...
private XPDelayedProperty document = new XPDelayedProperty();
[Delayed("document")]
public Byte[] Attachment {
get { return (Byte[])document.Value; }
set { document.Value = value; }
}
}
七、事务的支持:
事务在数据库程序内是不可或缺的。
显然该功能是由Session提供的。
Account account = new Account();
Session.DefaultSession.BeginTransaction();
try {
account.Amount = amount;
account.Save();
Session.DefaultSession.CommitTransaction();
}
catch (Exception e) {
Session.DefaultSession.RollbackTransaction();
account.Reload();
}
注意在Exception发生时,使用了Reload()。
八、保存之前的数据有效性检查:
class Account: XPObject {
public double Amount = DefaultAmount;
protected override void BeforeSave() {
base.BeforeSave();
if(!IsDeleted) {
if (Amount < 0) {
throw new Exception("Negative amount");
}
}
}
}
注意先判断了IsDeleted。
九、并发操作:
提供了检查对象在更改之前是否已经变化的检查机制。在以前我们必须自己写代码去检查,现在这也提供了。
十、数据分页机制
对大数据量,我们一般都不是一次提取的,而是分批提取,从而减少内存使用和加快提取速度。XPO提供了直接支持。但是它没有使用XPCollection,而是使用了另外一个类XPCusor。使用上和XPCollection差不多,也支持条件对象,但就是多了个分页支持。
这个设计思想令人纳闷,为什么不合二为一?
十一、对结构的持久化支持:
还是当前这个版本刚刚支持的。
public struct Point {
[Persistent("Abscissa")]
public int X;
public int Y;
}
public class Shape: XPObject {
public string Name = "";
[Persistent("Location")]
public Point Position;
}