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来表示类之间的”对多关系”。并且附加属性来表示多对多还是一对多的关系。
一对多:
在一个类中定义:
同时另一个类 Adresses 中定义:
public Customer Customer;
此时这个类的关联关系并没有指定关联类型,因为已经说明了类型。
可以在关联关系中进一步指定其他附加属性,比如级联删除关系:
...
[Association("CustomerOrders", typeof(Order)), Aggregated]
public XPCollection Orders { get { return GetCollection("Orders"); } }
...
}
多了Aggregated这种特性,表示聚集,也就是表之间的级联删除关系。
访问子表的方法:
枚举
foreach(Order theOrder in theCustomer.Orders)
增加子表的记录:
myCustomer.Orders.Add(myOrder);
三、查询:
使用条件对象:
比如: Freight < 10M
XPCollection orders = new XPCollection(typeof(Order), new BinaryOperator("Freight", 10M, BinaryOperatorType.Less));
这里使用了指定持久类类型的集合类XPCollection对象。这里并不意味着只能在一个表中查。如果这 个持久类和其他类有关联关系的话,那么条件对象可以包含对关联类的属性条件。也就是实现了多表查询。下面的组合查询中的Address就是Customer的一个聚集类。
复杂一点的条件使用GroupOperator。比如:BirthDate < 01/02/1960 AND Address.Street = ''10'
th Avenue''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'
th Avenue"));... new XPCollection(typeof(Customer), criteria)
以前也打算在项目中做一个类似此功能的组件,当时的出发点是想做一个和具体数据库sql语法无关的条件对象,用来代理sql条件语句的生成。后来由于项目成本原因作罢。现在终于有一个现成的了。
四、继承和多态:
某种程度上也可以说是表间关系。
XPO的持久类支持完整的继承和多态。
比如管理人员是一个员工,但一个员工不一定是管理人员。这个我们在以前数据库设计时可以在员工表中加一个是否管理人员的标识,或者另建一个管理人员表,再通过外键让它和员工表建立关系。
XPO中就可以用OO的方式来描述此类关系:建一个员工类,再建一个员工类的子类:管理人员类。
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;
}
注意使用了 Attribute ,结构才能持久化。
十二、对Attribute的总结:
应该看到,整个XPO框架中,Attribute使用的相当频繁,可以列出常用的:
[Aggregated] 没有参数
作用于a property or a field,并且只能是持久类的引用或者XPCollection.
实现两个持久类的级联删除的功能。
[Association("PersonAddresses", typeof (Address))]
作用于a property or a field
实现两个持久类的级联关系
参数:1、关联的名称;2、关联的持久类类型(但引用指明持久类型时,可以省略)
[MapTo("T_Person")]
作用于持久类或a property or a field
作用于持久类时:指定持久类映射的表名称。如果没有指定,默认的表名就是类名。
作用于property或field时:表的列名称。
参数是表名或列名,是区分大小写的。
[Persistent("CreatedOn")] 强制映射字段
作用于a property or a field
XPO一般只对持久类中的可写property和public field进行持久化操作。因此对只读的field可以加上该特性进行强制持久化。
参数:表名(可选)
[NonPersistent]
作用于a class, property or a field
指明不对类或property,field进行持久化操作。
参数无。
[DbType]
指定列创建时使用的数据类型。
参数:字符串的数据类型表示。
[MapInheritance(MapInheritanceType.OwnTable)]
作用于持久类。
两个持久类是继承关系时,持久信息的存储方式。
参数:
MapInheritanceType.OwnTable :各自独立的表,分别存储独有部分。
MapInheritanceType.ParentTable :都在父表存储。
[Delayed("document")]
只能作用于Property。
延迟数据装载,当持久类构造时并不装载数据,只是property首次被访问时才装载。
和类XPDelayedProperty结合使用才能实现延迟装载的功能。
参数:引用的XPDelayedProperty 类型field 名称。
[Size(SizeAttribute.Unlimited)] 数据长度
作用范围a property or a field。
指定映射的表字段长度。只是当创建字段时才体现。当字段已经存在,XPO此时不会检查该值。
参数:数值的长度或者SizeAttribute.Unlimited(不限制长度)
可以不指定。如果是string,默认是100。
[Key ] ,[Key(true)],[Key(AutoGenerate = true)]
作用于a property or a field。
指定主键。每个持久类都需要一个指定为key的propery or field。XPObject和XPBaseObject的一个明显差别就是XPObject已有一个OId主键
参数:bool型(可选)。是否由XPO自动生成键值(Int或GUID)。默认是false。
[Indexed]
作用于a property or a field。
指定索引。默认情况下改索引是非唯一索引。
没有构造参数。但可以通过set来设置为唯一索引[Indexed(Unique=true)]
[OptimisticLocking(false)]
作用于持久类。
是否乐观锁定(感谢format 的指错)。如果锁定,修改后提交时,XPO会检查原始数据是否已经被改变,而不是who lastest who win。XPO在持久类映射的表类加一个系统字段“OptimisticLockField”,显然是用来标记状态的。因此如果是映射到视图时,要显式设置为不锁定。
参数:bool值(可选,默认是true)
[ValueConverter(typeof(实现转换接口(抽象类ValueConverter)的类))]
作用于property。
值和类型转换,挺有意思的一个功能。从数据库提取值和保存值都可以作一个转换工作。
[NullValue("")]
作用于简单数据类型的a property or field。
指定对应库中值为null时,持久类中对应的数据。反之也是。
参数:根据不同的数据类型,引入不同的参数。看例子比较方便:
[ExplicitLoading]
ExplicitLoadingAttribute可以指定多层引用的递归层数,以减少数据库查询,提高性能。
{
public string Name = " A " ;
}
class B : XPObject
{
public string Name = " B " ;
[ExplicitLoading( 3 )]
public A A;
}
class C : XPObject
{
public string Name = " C " ;
[ExplicitLoading]
public B B;
}
class D : XPObject
{
public string Name = " D " ;
[ExplicitLoading]
public C C;
}
class E : XPObject
{
public string Name = " E " ;
public D D;
}
缺省情况下,我们检索到E对象时,系统会自动获取(构造)其引用的对象,当引用层数很深时,就会产生多条SQL语句。此时我们可以使用[ExplicitLoading]属性合并多个查询。在上面的例子中,D被做了属性标记,
那么会自动往下递归查找有该标记的引用。因此会将 D、C、B的查询构造到同一条SQL语句中,而A依然会被单独构造SQL,我们可以将B中的属性标记加上参数3,就可以将A也加到递归中,如此D、C、B、A合并到
同一条SQL语句中。注意这个Depth参数表示所引用类型距离Root的深度,A距离E有3层。
[MemberDesignTimeVisibility]
设计时是否显示
XPO的条件相关类。
XPO的条件对象用来生成数据筛选条件,实际就是SQL语句条件语法树(条件表达式的组合)的对象表示方法。
一、主要相关类:
1、继承于抽象类CriteriaOperator的类系列。
继承于CriteriaOperator的子类有:
BetweenOperator
取范围的条件表达式类,如:1000 <= Total Price <= 2000
BinaryOperator
二元表达式类,最基本的表达式,比如:TotalPrice>100
ContainsOperator
包含表达式类,比如:exists
GroupOperator
组合表达式类,利用它进行反复嵌套组合,可以组成任意复杂的条件树。
InOperator
在某个值列表范围内的表达式,可以认为是SQL中的in
NotOperator
取反表达式,对应SQL中的not
NullOperator
取空值表达式,对应SQL中的 IsNull
2、辅助CriteriaOperator的CriteriaOperand类系列。
继承于抽象类CriteriaOperand的子类有:
AggregateOperand
聚合操作,可以在此时使用各种聚合函数(以枚举方式提供),类似于groupby 再加Having
OperandProperty
表达式中的引用类成员(实体类(XPPersistent)的可持久化的属性(property)或字段(field))。对应的就是表字段。
OperandValue
表达式中的值
OperandValueBase
OperandValue的基类。
二、详细描述:
(A)、条件对象系列:
CriteriaOperator
条件运算类是所有条件对象的抽象基类。没有任何具体方法,只是在类上加了属性Serializable。(看来Dev是想让大家手动以序列化方式持久)。
BetweenOperator
范围运算类:用来表示某个值范围的条件表达式。
构造函数:
public BetweenOperator(CriteriaOperand objectProperty, CriteriaOperand leftOperand, CriteriaOperand rightOperand)
public BetweenOperator(string property, CriteriaOperand leftOperand, CriteriaOperand rightOperand) : this(new OperandProperty(property), leftOperand, rightOperand)
比如:1000 <= Total Price <= 2000 写成new BetweenOperator("TotalPrice", 1000, 2000))
BinaryOperator
二元运算对象,也就是一个二元运算表达式。
构造函数:
public BinaryOperator(CriteriaOperand opLeft, CriteriaOperand opRight, BinaryOperatorType type)
public BinaryOperator(string propertyName, object value, BinaryOperatorType type) :
this(new OperandProperty(propertyName), new OperandValue(value), type)
参数type的类型BinaryOperatorType是二元操作符枚举,看名称就知道意思啦。
BinaryOperatorType{ Equal, Greater , GreaterOrEqual , Less , LessOrEqual , Like, NotEqual }
比如:TotalPrice>100 写成: new BinaryOperator("TotalPrice", 10, BinaryOperatorType.Greater)
ContainsOperator
包含表达式:
这个原始文档是:Used in a search criteria to filter collection contents.
大家知道两个实体类之间是一对多或多对多时,其中一个对另一个类的引用是通过XPCollection类型实现的。对此类型的属性或字段的筛选就必须使用ContainsOperator
构造函数:
public ContainsOperator(OperandProperty objectProperty, CriteriaOperator operand)
public ContainsOperator(string property, CriteriaOperator operand) : this(new OperandProperty(property), operand)
比如两个类:
public class Customer : XPObject
{
/// <summary>
/// 有多个订单,是聚集关系。
/// </summary>
[Association("CustomerOrders", typeof (Order)), Aggregated]
public XPCollection Orders
{
get { return GetCollection("Orders"); }
}
}
/// <summary>
/// 订单
/// </summary>
public class Order : XPObject
{
/// <summary>
/// 订单的所属用户
/// </summary>
[Association("CustomerOrders")]
public Customer Customer;
/// <summary>
/// 订单金额
/// </summary>
public Decimal Freight;
}
这连个类是一对多关系,我想查询订单金额等于1000的用户。
XPCollection collection = new XPCollection(typeof(Customer),
new ContainsOperator("Orders ",new BinaryOperator("Freight ",1000,BinaryOperatorType.Equal));
实际是两个条件对象的组合使用。
GroupOperator
组合表达式类,非常强大,利用它进行反复嵌套组合,可以组成任意复杂的条件树。
可以组合CriteriaOperator类系列的任何类,包括自身。
构造函数:
public GroupOperator(CriteriaOperator[] operands);
Operands are grouped by the GroupOperatorType.And type.
public GroupOperator(GroupOperatorType type, CriteriaOperator[] operands);
比如:有这么四个类,用户、角色、地址、订单
它们之间的关系除用户和订单是一对多之外,其他都是多对多的关系。
/// <summary>
/// 用户
/// </summary>
public class Customer : XPObject
{
public string Name = "";
public string CompanyName = "";
public string Phone = "";
public string Fax = "";
[ValueConverter(typeof(CodeConvertor))]
public string Sex="2";
/// <summary>
/// 有多个订单,是聚集关系。
/// </summary>
[Association("CustomerOrders", typeof (Order)), Aggregated]
public XPCollection Orders
{
get { return GetCollection("Orders"); }
}
/// <summary>
/// 一个用户可以有多个地址
/// </summary>
[Association("CustomerAddresses", typeof (Address))]
public XPCollection Addresses
{
get { return GetCollection("Addresses"); }
}
/// <summary>
/// 角色,一个用户可以在多个角色中。
/// </summary>
[Association("CustomersGroups", typeof (Group))]
public XPCollection Groups
{
get { return GetCollection("Groups"); }
}
public Customer()
{
}
public Customer(string newName, string newCompanyName, string newPhone, string newFax)
{
Name = newName;
CompanyName = newCompanyName;
Phone = newPhone;
Fax = newFax;
}
}
/// <summary>
/// 地址
/// </summary>
public class Address : XPObject
{
public string Street = "";
/// <summary>
/// 一个地址可能有多个用户。
/// </summary>
[Association("CustomerAddresses")]
public Customer Customer;
public Address()
{
}
public Address(string theStreet)
{
Street = theStreet;
}
}
/// <summary>
/// 订单
/// </summary>
public class Order : XPObject
{
public DateTime OrderDate;
public DateTime RequiredDate;
public DateTime ShippedDate;
public int ShipVia;
/// <summary>
/// 订单金额
/// </summary>
public Decimal Freight;
/// <summary>
/// 订单的所属用户
/// </summary>
[Association("CustomerOrders")]
public Customer Customer;
public Order()
{
}
public Order(DateTime newOrderDate, DateTime newRequiredDate, DateTime newShippedDate, int newShipVia, Decimal newFreight)
{
OrderDate = newOrderDate;
RequiredDate = newRequiredDate;
ShippedDate = newShippedDate;
ShipVia = newShipVia;
Freight = newFreight;
}
}
/// <summary>
/// 角色
/// </summary>
public class Group : XPObject
{
public string Name = "";
/// <summary>
///一个角色拥有的用户
/// </summary>
[Association("CustomersGroups", typeof (Customer))]
public XPCollection Customers
{
get { return GetCollection("Customers"); }
}
public Group()
{
}
public Group(string newName)
{
Name = newName;
}
}
查找(角色是Administrator并且订单金额大于20) 或者 家庭地址在天津的用户的条件对象:
new GroupOperator
(
GroupOperatorType.Or,
new ContainsOperator("Addresses",new BinaryOperator("Street","天津",BinaryOperatorType.Equal)),
new GroupOperator
(
GroupOperatorType.And,
new ContainsOperator("Groups",new BinaryOperator("Name","Administrator",BinaryOperatorType.Equal)),
new ContainsOperator("Orders",new BinaryOperator("Freight",20,BinaryOperatorType.GreaterOrEqual))
)
)
InOperator
组成类似sql 表达式中in的表达式:
构造函数:
public InOperator(CriteriaOperand leftOperand, CriteriaOperand[] operands);
public InOperator(string propertyName, ICollection values);
很简单,不用举例
NotOperator
类似sql中的not操作。
构造函数:
public NotOperator(CriteriaOperator operand);
比如:new NullOperator("Company")
NullOperator
取空值。
构造函数
public NullOperator(string propertyName);
public NullOperator(OperandProperty operand);
比如:new NotOperator(new NullOperator("Company"))
(B)、条件对象的操作符系列:
CriteriaOperand
条件对象的操作符,包含运算数,运算符,运算对象等等,是个抽象类。
AggregateOperand
聚合操作,可以在此时使用各种聚合函数(以枚举方式提供),类似于groupby 再加Having 。
构造函数 :
public AggregateOperand(OperandProperty objectProperty, OperandProperty aggregateProperty, Aggregate type, CriteriaOperator criteria);
还是那些类,想查找订单金额总数小于100的人
new XPCollection(typeof(Customor), new BinaryOperator(new AggregateOperand(new OperandProperty("Orders"), new OperandProperty("Freight "),Aggregate.Sum, null), new OperandValue(100), BinaryOperatorType.Less))
可以看到使用了聚合函数类型枚举
public enum Aggregate{ Avg , Count , Max ,Min , None }
或者地址有类似于上海,并且该地址是唯一的:
new XPCollection(typeof(Cutomor), new BinaryOperator(new AggregateOperand("Addresses",
new BinaryOperator("Street", "上海%", BinaryOperatorType.Like)), new OperandValue(1), BinaryOperatorType.Equal))
OperandProperty
表达式中的引用类成员(实体类(XPPersistent)的可持久化的属性(property)或字段(field))。对应的就是表字段。
构造函数:
public OperandProperty(string propertyName);
例子上面都有了,要注意非持久化的类成员是不可以直接使用的。
OperandValue
表达式中的值。
构造函数:
public OperandValue(object value);
没有什么好说的。
OperandValueBase
OperandValue的基类。基本没用。
XPO 使用记录
1. 一般实体类直接继承自 XPObject,如果需要自定义主键或自增字段则可继承自XPCustomObject。
2. 继承自XPBaseObject的实体类,Delete后将被直接物理删除。而XPCustomObject & XPObject的继承类则是软删除。
(1) 实体对象软删除
customer.Delete();
customer.Save();
(2) 物理删除
Session.DefaultSession.PurgeObject(customer);
(3) 物理删除所有被标记软删除的记录。
Session.DefaultSession.PurgeDeletedObjects();
(4) 清除数据库中所有数据。
Session.DefaultSession.ClearDatabase();
(5) 恢复被软删除的记录。
while(true)
{
o = (MyObject)Session.DefaultSession.FindObject(typeof(MyObject), new NotOperator(new NullOperator("GCRecord")), true);
if (o == null)
{
break;
}
o.GCRecord = null;
o.Save();
}
(6) 批量删除
XPCollection customers = new XPCollection(typeof(Product), new BinaryOperator("Customer", "Tom"));
Session.DefaultSession.Delete(customers);
Session.DefaultSession.Save(customers); // persist deletion 我的1.58版本Session.Save()没有重载这个方法????