[转载]AAF灵便应用框架简介系列(3):对象模型
AAF对象模型的核心是IAgileObject接口及其实现。所有Business Object在使用AAF时都应直接或间接继承自IAgileObject的基本实现:AgileObject。本文的内容看起来似乎会较多较复杂,但在 实际应用中,绝大多数时候都不需要关心这些内容。本文的目的只是为了让大家对AAF的内核模型有所了解,如果大家觉得枯燥可以粗略读一下,有点基本概念即 可。下面首先看看IAgileObject接口。
IAgileObject接口定义如下:
public interface IAgileObject : IIdentifiable
{
// 灵便对象的Id默认采用UUID,也可以由外部指定或者根据一定的组装格式组装(如订单条目Id可以由订单编号及产品
// 编号组装而成)。在指定了Id由组装而成时,当外部代码显失设置好了组装所有的信息后,可以显式调用本方法,虽然
// 不调用也不会有任何问题,但是外部代码必须保证在保存(包括因为保存其他对象而连带保存本对象)前组装所需的
// 信息都已提前准备好。如果有必要的话,这一话题在后续介绍中还会详细展开。一般情况下,不建议使用Id的组装功能。
// 默认地,Id长度为32个字符。考虑到缓存等特性,Id总是在对象被保存前被手工或自动创建,而不会等到保存到数据库
// 后再由数据库创建。目前AAF限制所有AgileObject的Id必须为字符串。
void AssembleId();
// 对象是否为批量加载起来的
bool GetIsBatchLoaded();
// 获取该对象的最后刷新时间
DateTime GetLastRefreshed();
// 判断一个值是否可被作为该对象的Id。
bool IsIdAcceptable(object id);
void Recover();
// 该方法由内核调用,一般不要操作这个方法
void ResetDirty(bool includeRelations, bool includeChildren);
// 该方法由内核调用,一般不要操作这个方法
void ResetDirty(bool includeRelations, bool includeChildren, bool rollback);
// 该方法由内核调用,一般不要操作这个方法
void ResetNew();
// 该方法由内核调用,一般不要操作这个方法
void ResetRelationsDirty(bool includeRelItems, bool includeChildren);
// 该方法由内核调用,一般不要操作这个方法
void ResetRelationsDirty(bool includeRelItems, bool includeChildren, bool rollback);
// 该方法由内核调用,一般不要操作这个方法
void SetIsBatchLoaded(bool val);
// 挂起一个数据,执行本操作后,AgileObject的IsSuspent属性为true,外部应用可以根据此属性来决定挂起的具体含义。
void Suspend();
// 该方法由内核调用,一般不要操作这个方法
void TouchLastRefreshed();
// 对象的类型全名(含程序集名),如:”XX.Order.Imp.Order, XX.Order.Imp”
string FullTypeName { get; }
// 获取或设置对象Id,一般不应从外部设置对象Id,如果是外部设置,外部程序应保证Id在特定范围内的唯一性。对象的
// Id只能被有效设置一次。有效设置之后的再次设置将会被拒绝。考虑到同一类型的多个衍生类型可以被保存到同一个数
// 据表中,在外部设置Id时,其唯一性要求需要仔细考虑。外部不设置对象Id时,系统在恰当的时机(某处读取对象Id时,
// 对于内核而言,在保存对象时是需要读取对象Id的)会自动创建对象Id。对象Id默认为转化为字符串的32位UUID。
object Id { get; set; }
// 是否被挂起或删除。对象的删除有两种形式:一种是彻底删除,一种是加删除标记(通过将IsDeleted设置为true)。具
// 体某种对象是哪一种情况,取决于该对象类型的AgileObjectStorageAttribute的IsDeletePermanently属性。如果数据对
// 象未被删除或挂起,则返回true,这具体意味着什么,则由应用自行决定。
bool IsActive { get; }
// 数据是否可删除,提供一个有用的开关,当本属性为true时,IsDeleted无法被改变为true。
bool IsDeletable { get; set; }
// 删除标记
bool IsDeleted { get; set; }
// 对象在上次保存、刷新、加载以来,IsDeleted属性是否发生改变。
ChangeType IsDeletedChange { get; }
bool IsDirty { get; }
bool IsLoading { get; set; }
bool IsLoadingRelas { get; set; }
// 是否是一个新创建(新加载的对象不算“新”对象)并且还没有保存的对象。
bool IsNew { get; }
// 挂起标记
bool IsSuspent { get; set; }
// 对象在上次保存、刷新、加载以来,IsSuspent属性是否发生改变。
ChangeType IsSuspentChange { get; }
// 提供一个索引器,使得在需要时外部可以通过本索引器以及属性的名称来访问个各属的值。这在一些无法预知对象的实际
// 具体类型,而只知道该对象是一个AgileObject时是有用的。如我们可以通过agileObject["Name"]达到获取agileOject对象
// 的Name属性的目的。
object this[string name] { get; set; }
// 对象的最后一次更新提交时间。对象可以被随时通过调用IPersister的SaveAgileObject/SaveObjects方法保存。但真正的
// 保存只发生在对象确实有属性变更时,只有在这种棋况下,LastModified才会在保存时发生改变,否则什么都不会发生,
// 对象也根本不会被保存。
DateTime LastModified { get; set; }
// 可通过这个数据词典获取对象上次保存、刷新或加载的各属性取值。
IDictionary OriginalValues { get; }
// 数据的存储别名。一种对象可以被持久化在多个数据表甚至多个数据库中,本属性记录了该对象来自哪一个存储别名。本属性
// 由AAF内核维护,应用程序可以读取,但一般情况下情不要修改。
string StorageAlias { get; set; }
// 获取数据对象的Id
string StringId { get; }
// 获取数据对象的类型描述(描述对象有那些字段,有哪些关系和关联)
ITypeDescription TypeDescription { get; set; }
…
}
在实际应用中,AgileObject对象之间总是存在着各种关系和关联。那么什么是关系?什么是关联?二者在AAF中如何表述呢?下面我们一起来回答这几个问题。
什么是关系。简而言之,关系就是两个灵便对象之间的一对多,多对多关系。在具体表述上,其形式为一个对象被标识为另一个对象的一个 IAgileRelation。大家可能还记得我在AAF介绍系列的开篇前言中给出的Order/OrderItem的示例。在该示例中,通过在 Order中的这两句代码定义了一个Order及OrderItem之间的一对多关系:
[Relation(ChildType = typeof(OrderItem), RelationType = typeof(FreeAgileRelation)]
public IAgileRelation OrderItems;
其中RelationAttribute的ChildType属性声明了这一一对多关系的“多”为OrderItem。 RelationAttribute的RelationType有两种取值,其一为:typeof(FreeAgileRelation),其二为 typeof(SimpleAgileRelation)。后者在多应用实例情况下进行缓存同步有一定的难度,因此,目前已经不推荐使用。一般情况下直接 写作RelationType = typeof(FreeAgileRelation)即可。
实际上,IAgileRelation不仅仅支持一对多的关系。通过RelationAttribute的更多属性设 置,IAgileRelation可以支持多对多甚至多对多对多(也就是在多对多的基础上,两个具体实例之间还可以同时有多个“同名”关系,所谓同名关系 就是说存放在同一个类型的同一个IAgileRelation实例中的关系)。也就是说IAgileRelation甚至可以被用来描述复杂的连接图。 IAgileRelation的接口定义如下:
public interface IAgileRelation : ICollection, IEnumerable
{
// 当新增了关系项时
event RelationAddRemoveDelegate RelationAdded;
// 当某个关系项被移除时
event RelationAddRemoveDelegate RelationRemoved;
// 新增与一个对象的关系(内部自动新增一个关系项)
void Add(IAgileObject child);
// 内核使用,应用一般无需使用
void AddRange(IEnumerable children);
// 删除所有关系项
void Clear();
// 是否包含一个指向该子对象Id的关系项
bool Contains(string id);
// 将所有有关系的子对象复制到一个数组中
object[] CopyTo();
// 将所有有关系的子对象复制到一个数组中的某个位置
void CopyTo(Array array, int arrayIndex);
// 将所有有关系的子对象复制到一个指定类型的数组中的某个位置
object CopyTo(Type arrayType, int index);
// 过时方法,同 Clear
void Delete();
// 过时方法,同 Clear
void DeleteDelayed();
// 是否包含一个指向该子对象的关系项
bool ExistChild(IAgileObject child);
// 从同一个IAgileRelation形成的对象体系的更高处(向上寻找最高点对象,该对象类型由scope指定)向下寻找一个对象是否
// 存在
bool ExistUnderType(IAgileObject parent, IAgileObject child, Type scope);
// 获得一个子对象在关系中的位置索引(自0起)
int GetItemIndex(IAgileObject child);
// 获得所有关系项的枚举器(可以指定是否包括,已被标记移除,但是尚未保存提交的关系项,在类似枚举器中,默认不包括
// 已移除的)
IEnumerable GetItems(bool includeRemoved);
// 获得属于指定类型的所有关系项的枚举器
IEnumerable GetItems(Type type);
// 获得属于指定类型指定状态的所有关系项的枚举器
IEnumerable GetItems(Type type, AgileObjectTypeFilteredEnumerator.StatusFiltered status);
// 获得属于指定类型指定状态的所有关系项的枚举器,并可以决定是否包括指定类型的衍生类型
IEnumerable GetItems(Type type, AgileObjectTypeFilteredEnumerator.StatusFiltered status, bool isSubTypeEnumerable);
// 获得属于指定类型的所有关系项的枚举器,并可以决定是否包括指定类型的衍生类型
IEnumerable GetItems(Type type, bool isSubTypeEnumerable, bool includeRemoved);
// 各参数意义与前面几个方法类似
IEnumerable GetItems(Type type, AgileObjectTypeFilteredEnumerator.StatusFiltered status, bool isSubTypeEnumerable, bool includeRemoved);
// 在指定的位置增加一个指向child子对象的关系项
void Insert(int index, IAgileObject child);
// 将子对象的关系项向上移动一个位次
bool MoveTo(IAgileObject obj, bool up);
// 将指定位置关系项对应的子对象移动到目标位置
void MoveTo(int fromIndex, int toIndex);
// 移除一个与子对象的关系(所有关系项移除默认地并不导致子对象本身被删除)
void Remove(IAgileObject child);
// 移除一个与子对象的关系,如果dontDeleteChild为false,则子对象也会被删除,但是系统并不负责删除该子对象通过其他可
// 能存在的关系与其它可能存在的父对象之间的关系项,这种清理工作属于应用开发的任务。当然,如果不作这种清理的话,即
// 使一个关系项指向的子对象已被彻底删除,内核也不会显式丢错。
void Remove(IAgileObject child, bool dontDeleteChild);
// 移除与子对象的所有关系
void RemoveAll();
// 移除与子对象的所有关系时,可以指定是否删除子对象,其考虑与前述类似
void RemoveAll(bool dontDeleteChild);
// 移除指定位置的关系项
void RemoveAt(int index);
// 移除指定位置的关系项时,可以指定是否删除子对象,其考虑与前述类似
void RemoveAt(int index, bool dontDeleteChild);
// 通过子对象编号移除关系项
void RemoveById(string id);
// 通过子对象编号移除关系项,可以指定是否删除子对象,其考虑与前述类似
void RemoveById(string id, bool dontDeleteChild);
// 通过关系项编号移除关系项,可以指定是否删除子对象,其考虑与前述类似,但还必须进一步考虑多连接的情况
int RemoveByRelationItemId(string id, bool dontDeleteChild);
// 内核使用,应用请勿调用
void ResetChildrenDirty();
// 内核使用,应用请勿调用
void ResetChildrenDirty(bool rollback);
// 内核使用,应用请勿调用
void ResetDirty(bool includeRelItems, bool includeChildren);
// 内核使用,应用请勿调用
void ResetDirty(bool includeRelItems, bool includeChildren, bool rollback);
// 设置一个回调方法,在试图增加一个关系项时,内核会回调该方法,该方法可以拒绝增加一个关乎项
void SetCheckAddableDelegate(CheckAddableDelegate newDelegate);
// 设置一个回调方法,在试图移除一个关系项时,内核会回调该方法,该方法可以拒绝移除一个关乎项
void SetCheckDeletableDelegate(CheckDeletableDelegate newDelegate);
// 内核使用,应用请勿调用
void SetInfo(IRelationDefinition RelationDefinition, IAgileObject parent);
// 挂起一个子对象对应的关系项(注意不是挂起一个子对象)
void Suspend(IAgileObject child);
// 挂起指定位置的关系项
void SuspendAt(int index);
// 关系项数目
int Count { get; }
// 关系项或子对象自上次保存、加载、刷新以来是否有所改变
bool IsDirty { get; }
// 通过子对象Id访问关系子对象的索引器
IAgileObject this[string id] { get; }
// 通过关系项索引访问关系子对象的索引器
IAgileObject this[int index] { get; }
// 父对象
IAgileObject Parent { get; }
// 父对象Id
object ParentId { get; }
// 关系描述,与IAgileRelation的关系类似于ITypeDescription与IAgileObject的关系
IRelationDefinition RelationDefinition { get; }
// 获取所有被移除(但还未提交)的子对象
IEnumerable RemovedChildren { get; }
…
}
与关系相对应的一个概念是关系项。如果说关系定义了两种灵便类型之间的联系的话。关系项则定义了这种联系的持久化类型。默认的,所有关系的关系项类型为:FreeAgileRelationItem。下面是FreeAgileRelationItem基本定义:
[AgileObjectStorage(IsDeletePermanently=true), AgileObject(UseCache=false, AutoGenerateIdIfNeeded=true)]
public class FreeAgileRelationItem : AgileObject
{
// 子对象Id(IAgileRelation声明中RelationAttribute的ChildType指定的对象类型)
public string ChildId { get; set; }
// 父对象Id(IAgileRelation声明所在类型对象)
public string ParentId { get; set; }
// 子对象
public IAgileObject Child;
// 子对象类型
public Type ChildType;
// 子对象全名(含程序集名后缀)
public string ChildTypeFullName;
// 子对象在关系中的序号(由内核自行维护,应用可以获取该信息,但不应直接更改,IAgileRelation的增加、删除、移动关系
// 项对象方法会自动导致本属性的改变)
public int ItemIndex;
// 父对象
public IAgileObject Parent;
// 父对象类型
public Type ParentType;
// 父对象全名(含程序集名后缀)
public string ParentTypeFullName;
// 关系名(实际上就是在父对象中定义的IAgileRelatin成员名,如前言示例的“OrderItems”)
public string RelationName;
}
通过其基本定义,我们可以得出结论:FreeAgileRelationItem本身只是提供了两种关系之间的关系的基本记录。应用可以针对某个关系定义 其特定的关系项类型,在该关系项类型上增加更多的属性和方法。新增的关系象类型必须继承自FreeAgileRelationItem,并且通过 RelationAttribute的RelationItemType设置。下面是进行该属性设置的简单例子:
[Relation(ChildType = typeof(OrderItem), RelationType = typeof(FreeAgileRelation), RelationItemType = typeof(Order_OrderItemRelationItem)]
public IAgileRelation OrderItems;
下面我们再来看看IRelationDefinition:
public interface IRelationDefinition
{
// 关系项为之所以字段名(不是数据库字段名,数据库字段名默认为对象字段名,但可以通过配置改变,AAF的一个显著特点是
// 默认无需定义任何映射文件),默认为ItemIndex,一般请勿改变本属性
string ChildIndexFieldName { get; }
// 前述字段对应的IAgileMember描述,IAgileMember有点类似于.Net Reflection的MemberInfo
IAgileMember ChildIndexMember { get; set; }
// 已过时
string ChildParentFieldName { get; }
// 已过时
string ChildParentIdFieldName { get; }
// 已过时
IAgileMember ChildParentIdMember { get; set; }
// 已过时
IAgileMember ChildParentMember { get; set; }
// 已过时
string ChildParentsCountFieldName { get; }
// 已过时
IAgileMember ChildParentsCountMember { get; set; }
// 子对象类型
Type ChildType { get; }
// 子对象类型描述
ITypeDescription ChildTypeDescription { get; }
// “对偶”关系定义,父对象定义的关系可以与子对象的一个关系建立对偶,便于子对象了解其本身在该关系下对应于哪些父对象
// 对偶关系一般被标注为无需加载和保存。内核会根据原关系的变化自动维护对偶关系的关系项。
IRelationDefinition Couple { get; }
// 对偶关系名称
string CoupleName { get; }
// 本关系是否为对偶关系
bool IsCouple { get; set; }
// 是否允许加入Relation定义时通过RelationAttribute的ChildType方法指定的类型的衍生类型对象
bool IsSubTypeAcceptable { get; }
// 已过时
string ParentChildrenCountFieldName { get; }
// 已过时
IAgileMember ParentChildrenCountMember { get; }
// 已过时
IAgileMember ParentRelationMember { get; }
// 父对象类型,取决于关系定义在什么类型上。因此关系与对偶关系的子对象父对象是倒过来的
Type ParentType { get; }
// 父对象类型描述
ITypeDescription ParentTypeDescription { get; }
// 关系项类型
Type RelationItemType { get; }
// 关系项类型描述
ITypeDescription RelationItemTypeDescription { get; set; }
// 关系级别,决定了关系是一对多,还是多对多,还是多对多对多,默认为一对多
RelationLevel RelationLevel { get; }
// 关系名
string RelationName { get; }
// 关系类型,父对象、子对象、关系项对象都是灵便对象类型,所以都有对应的对象描述。关系类型不是灵便对象类型。
// 因此,只有类型(.Net Reflection提供)没有类型描述(AAF内核提供)
Type RelationType { get; }
}
与关系相对,关联就是两个灵便对象之间的一对一关系。在具体表述上,其形式为一个对象被标识(或相互标识)为另一个对象的一个属性。我们可以在前言中的Order类型上增加一个指向User的关联。
private User _user;
public User User
{
get
{
return _user;
}
set
{
_user = value;
}
}
AAF的基本数据对象模型就是围绕上述基本概念展开的。至于扩展属性/衍生属性等AgileObject的高级特性将留待将来介绍。
下面我们对ITypeDescription作一个简单介绍。
ITypeDescription有两种,一种是普通AgileObject对象对应的ITypeDescription,另一种则是关系项型 AgileObject对应的ITypeDescription。在前一种情况下,基本上一种对象类型对应一个ITypeDescription,在后一 种情况下,一种对象类型可以对应多个ITypeDescription,具体对应于多少个,取决于该对象类型出现在多少个关系声明中(同一个父对象也可以 有多个关系声明)。可以通过ITypeService的GetObjectTypeDescription(Type type)方法获取一个普通对象类型的类型描述。可以通过ITypeService的 GetRelationTypeDescription(Type aType/*父对象类型*/, Type bType/*子对象类型*/, Type relationItemType/*关系项类型*/, string name/*关系名*/)方法获取一个关系项类型在指定关系下的类型描述。
ITypeService是AAF内核提供的一个AAF类型描述及相关服务接口。其接口定义如下:
public interface ITypeService
{
// 清除置换类型
void ClearReplaceType(Type type);
// 创建一个类型的实例,类似于new操作符,不同的是AAF内核将检查有没有为该类型指定一个置换类型(必须是原类型的
// 衍生类型),如果存在置换类型,则实例化该类型并返回,否则返回原类型的一个新实例。
IAgileObject CreateInstance(Type type);
// 获取一个程序集中的所有灵便对象类型
Type[] GetAssemblyAgileObjectTypes(Assembly assembly);
// 通过一个程序集的不完整(不包含版本等后缀)名称获取一个程序集
Assembly GetAssemblyByPartialName(string assemblyName);
// 获取调用本方法的程序集中的所有灵便对象类型
Type[] GetCallingAssemblyAgileObjectTypes();
// 获取一个类型的加了程序序集名后缀的完整类型名。
string GetFullTypeName(Type type);
// 获取一个普通灵便对象的类型描述
ITypeDescription GetObjectTypeDescription(Type type);
// 内核使用
ITypeDescription GetObjectTypeDescriptionByKeyName(string key);
// 获取一个关系项灵便对象的类型描述
ITypeDescription GetRelationTypeDescription(Type aType, Type bType, Type relationItemType, string name);
// 内核使用
ITypeDescription GetRelationTypeDescriptionByKeyName(string key);
// 获取指定关联名对应的关联Id字段名(内核会自动为灵便对象的每个关联对象创建一个扩展字段:关联Id字段,该字段
// 在数据库中可见,内核会自动加载和保存该字段)
string GetRelativeIdMemberName(string relativeName);
// 查阅一个类型的置换类型
Type GetReplaceType(Type type);
// 通过加程序集名后缀的类型名获得一个类型
Type GetType(string typeFullName);
// 内核使用
void InitiateRelationDefinitions();
// 内核使用
void InitiateRelationDefinitions(ITypeDescription[] typeDescriptions);
// 内核使用
ITypeDescription[] RegAoTypesAndIniRelDefs(Type[] types);
// 内核使用
ITypeDescription[] RegisterAgileObjectTypes(Type[] types);
// 设置一个类型的置换类型
void SetReplaceType(Type oldType, Type newType);
// 本类型描述对应的加程序集名后缀的类型名
string FullTypeName { get; }
// Id字段名,目前总是“Id”
string IdFieldName { get; }
// 所有的普通灵便对象,其关键字格式为内核所保留
IDictionary ObjectTypeDescriptions { get; }
// 所有的关系项灵便对象,其关键字格式为内核所保留
IDictionary RelationTypeDescriptions { get; }
}
在前言中的例子中,我们已经看到,ITypeService的具体实现可以通过下述代码获取:
ITypeService typeService = return (ITypeService)ServiceHub.Instance.GetService(typeof(ITypeService));