实体开发框架
实体(BE)的基本概念
BE,即Business Entity,指领域模型中的业务数据对象,如:订单头,客户,地址,国家等
BE的设计工作通过UBF Studio完成。在一个模型图中,我们会设计实体,属性类型,枚举类型,关联,继承,组合,效验,异常,事件等等相关的东西。
实体的对外结构基本组成
每一个强类型的实体都有以下几个类:
实体类,如A_Ass1to1
实体的Key(强类型的EntityKey): A_Ass1to1.EntityKey
实体的查询类Finder:A_Ass1to1.EntityFinder
实体的强类型集合EntityList:A_Ass1to1.EntityList
实体的资源属性和强类型访问属性的辅助类
实体弱类型的EntityKey
弱类型EntityKey是BusinessEntity的内部类,也是强类型EntityKey的基类,主要涉及标识一个实体两个关键的属性:ID和EntityType,并对外提供一个GetEntity()的公开方法
实体内部数据存储
在实体内部,自身的数据为基本类型,保存在集合InnerData中,关联数据为对象或集合类型,保存在集合InnerRelation中。这两个对象都提供了一些事件和方法,如GetValue/SetValue,GetRelation/SetRelation等用来对实体进行较高级的控制(如弱类型操作)。由于强类型的方式使用BE比较简单直观,在生成实体代码时,会提供强类型的访问方式,所以,开发人员一般不会直接访问实体的内部数据
实体的CopyTo
为了支持实体数据的复制,可以利用ICopyable接口,基类Entity支持该接口,可以通过实例方法CopyTo,将实体中的数据复制给target。
CopyTo对外提供两种方法:
CopyTo(Entity target),这种方式默认是不拷贝ID
CopyTo(IPersistableObject po, bool isCopyKey),这种方式可以由第二个参数决定是否要拷贝ID
注意:
目前CopyTo方法只完整实现了对基本属性的拷贝,对于关联实体,实际上是不拷贝的,对于一对一或一对多的情况,会出现关联对象也拷贝的假象,实际上,对象是懒加载上来的,对于一对多的情况,是空对象,以后根据实际的使用情况,决定是不是要完整实现关联实体的拷贝
CopyTo方法只是将相关的业务数据拷贝,不涉及到一些系统的控制属性,如SysState,NeedPersistable等系统的控制字段,不涉及到OriginalData
实体的OriginalData
实体带有一个OriginalData的属性,保存实体在数据库中的原始值,OriginalData反映的是实体在数据库中的映像,初始值是一个空的实体对象,只有在查询,新建和修改操作成功后,才会刷新OriginalData,保持和数据库一致,需要详细说明的是,新建和修改时刷新OriginalData的动作在基类的 OnInserted事件和OnUpdated事件之后,所以,在生成的XXX Extend.cs文件中,如果在后事件中要访问旧值,需要注意前后顺序
通过实体的GetChangedAttributes方法可以返回变化后的简单属性的集合,注意是简单类型,不包括集合属性,属性类型。
示例:
using (ISession s = Session.Open())
{
Yel_Ass1to1_A a = Yel_Ass1to1_A.Create();
a.Name = "OldValue";
s.Commit();
a = Yel_Ass1to1_A.Finder.Find("ID = " + a.ID);
a.Name = "NewValue";
Assert.AreEqual(1, a.GetChangedAttributes().Count);
a.Code = 123;
Assert.AreEqual(2, a.GetChangedAttributes().Count);
a.Name = "aaa";
Assert.AreEqual(2, a.GetChangedAttributes().Count);
a.Name = "OldValue";
Assert.AreEqual(1, a.GetChangedAttributes().Count);
a.Remove();
s.Commit();
}
这里有几个需要提醒开发人员注意的事项:
- OriginalData刷新的时机:
对于新建和修改操作,OriginalData是在OnInserted/OnUpdated方法运行完后刷新,对于查询,OriginalData是在查询后刷新,对于删除,OriginalData没什么意义,取决于删除对象的加载方式,简单说,就是在OnSetDefaultValue/ OnInserting/ OnInserted中,新建对象的OriginalData是一个id为0的空实体对象,在OnSetDefaultValue/ OnUpdating / OnUpdated中,修改对象的OriginalData是旧对象
- 为什么我访问某实体对象的OriginalData的属性为空
首先,看OriginalData对象的ID,如果为0,表示是一个空对象,所有的属性为默认值,请参考OriginalData刷新的时机判断这个访问操作是不是新建,这个对象是标准的查询出来的还是通过CopyTo的方式构造的(实体基类的CopyTo不涉及OriginalData)
如果OriginalData有有效的ID,检查这个对象的加载方式,看看是不是特殊的加载方式引起属性为空
- OriginalData的关联对象
OriginalData 和CopyTo方法很类似,只完整实现了对基本属性的拷贝,对于关联实体,实际上是不拷贝的,这样,出现 aEntity.OriginalData.bEntity的访问方式,对bEntity,是懒加载上来的
实体的状态SysState
实体的状态,为枚举对象,开发人员主要关心以下4个枚举值: Inserted,Updated,Deleted,Unchanged,分别对应实体的新建,修改,删除,不变化(查询返回的初始状态)
是否需要持久化 NeedPersistable
NeedPersistable表示这个对象是否需要持久化,如果为false,则这个对象将不会参与持久化
Session
基本概念和使用方式
在现在UBF中,Session的本意是work unit,即持久层的一个边界,非常轻,主要用作批量提交,并标识这次批量提交的边界。在session里面的CUD操作会受持久层控制,而seesion外实体是不受控制的,所以,做CUD操作要在session里面完成,一般建议一个BP调用最好都有一个大session包住,这样,在里面的操作就不用多次开Session,性能会好些
- 关于事物,session仅仅是一个持久层边界,不涉及到事务等概念,目前UBF的事物支持在AOP上定义,可以在BP上设置
- 当发生session嵌套的情况时,每次提交都是真正提交
using (ISession session1 = Session.Open ()){
...
using (ISession session2 = Session.Open ()){
...
session2.Commit(); //提交更改,但只是Session2范围内的修改更新
}
session1.Commit(); //提交更改,只处理Session1的修改更新
}
- 当前ISession可以通过Session的Current属性获得,每调用一次Session的Open方法,Current属性都会被更新
- 在一个Session范围内,可以分步提交,
using(ISession session1 = Session.Open ()){
Ass1to1 objA1 = A_Ass1to1.Create();
session1.Commit(); //提交objA1的新建动作
Ass1to1 objA2 = A_Ass1to1.Create();
session2.Commit();//提交objA2的新建动作
}
对外接口
- Create(IEntity entity)
将entity对象的状态设置为UFSoft.UBF.PL.Engine.ObjectState.Inserted,并加入到当前的session中
- Modify(IEntity entity)
将entity对象的状态设置为UFSoft.UBF.PL.Engine.ObjectState. Updated,并加入到当前的session中
- Remove(IEntity entity)
如果当前session有这个entity,且状态为insert,则从当前session中移出这个对象,其他情况下,将entity对象的状态设置为UFSoft.UBF.PL.Engine.ObjectState. Deleted,并加入到当前的session中
- InList(IEntity entity)
将entity加入到当前的session中
objA = A_Ass1to1.Create();
objA.Name_A_1to1 = "Test";
using (ISession s = Session.Open())
{
s.InList(objA);
s.Commit();
}
Assert.IsNotNull(A_Ass1to1.Finder.Find("ID = '" + objA.ID + "'"));
- DeList(IEntity entity)
将entity从session中移出
using (ISession s = Session.Open())
{
objA = A_Ass1to1.Create();
objA.Code_A_1to1 = 333;
s.DeList(objA);
s.Commit();
}
Assert.IsNull(A_Ass1to1.Finder.Find("ID = '" + objA.ID + "'"));
实体的操作
增加实体
- 简单实体:
模型如下,对实体A操作
- 常规做法:
通常我们新建实体是要求在session里面建立:
using (ISession session = Session.Open())
{
obj = A_Ass1to1.Create();
...
session.Commit();
}
- 特殊做法:
遇到一些特殊情况,需要在session外面new一个对象时。这种做法很特殊,需要显式在一进入sesssion时调用session.Create(obj),不推荐
A_Ass1to1 obj = new A_Ass1to1();
using (ISession session = Session.Open())
{
//一进入session,操作对象之前(包括设置实体的状态,赋值等所有的相关的操作之前)
//要显示调用session.Create(obj)
session.Create(obj);
obj.ID = 111;
obj.Name_A_1to1 = "aaa";
session.Commit();
}
- 主从实体:
- 1对1,其中A_Com1c1为主实体,B_Com1c1为非主实体,注意红字的写法
A_Com1c1 objA;
B_Com1c1 objB;
using (ISession session = Session.Open())
{
objA = A_Com1c1.Create();
objA.Code_A_Com1c1 = 1000;
objA.Name_A_Com1c1 = "objA";
objB = B_Com1c1.Create(objA);
objB.Code_B_Com1c1 = 1001;
objB.Name_B_Com1c1 = "objB";
session.Commit();
}
- 1对多:其中A_Com1cN为主实体,B_Com1cN为非主实体,注意红字的写法。对于非主实体,有两种新建方式,这两种方式都只支持
A_Com1cN objA;
B_Com1cN objB;
using (ISession session = Session.Open())
{
objA = A_Com1cN.Create();
objA.Code_A_Com1cN = 111;
//方式一
objB = B_Com1cN.Create(objA);
objB.Code_B_Com1cN = 222;
objB = B_Com1cN.Create(objA);
objB.Code_B_Com1cN = 333;
//方式二
objB = objA.B_Com1cN.AddNew();
objB.Code_B_Com1cN = 444;
objB = objA.B_Com1cN.AddNew();
objB.Code_B_Com1cN = 555;
session.Commit();
}
查询实体
(关于OQL和参数的用法,见实体查询这个章节)
- 查单个实体:下面的操作为查询一条ID为“111”的纪录
FindByID方法是先从缓存里面取,缓存没有再去数据库取,
A_Com1cN obj = A_Com1cN.Finder. FindByID(111);
Find方法是直接从数据库取
A_Com1cN objA = A_Com1cN.Finder.Find("ID = 111");
- 查实体集合
FindAll方法是直接从数据库取,参数为OQL语句
A_Com1c1.EntityList list = A_Com1c1.Finder.FindAll("");
修改实体
- 简单实体
- 常规做法
通常我们修改实体是要求在session里面建立
using(ISession session = Session.Open ()){
Customer obj = Customer.Finder. FindByID(id);
if(obj!=null){
obj.Name = "new name";
}
... ...
session.Commit();
}
- 特殊做法
遇到一些特殊情况,需要外面传进的实体时,需要显式调用session. Modify(obj),不推荐
using (ISession session = Session.Open())
{
session.Modify(obj);
session.Commit();
}
- 主从实体:
- 1对1
- 1对多
using (ISession session = Session.Open())
{
objA = A_Com1cN.Finder.Find("ID = '" + objA.ID + "'", null);
B_Com1cN.EntityList bList = objA.B_Com1cN;
bList[0].Name_B_Com1cN = "aaa";
bList[1].Name_B_Com1cN = "bbb";
session.Commit();
}
删除实体
- 简单实体
using(ISession session = Session.Open ()){
Customer obj = Customer.Finder.FindByKey(id);
if(obj!=null){
obj.Remove();
}
... ...
session.Commit();
}
- 主从实体:
删主实体会自动全部删除从实体
using (ISession session = Session.Open())
{
objA = A_Com1cN.Finder.Find("ID = '" + objA.ID + "'", null);
//会自动删除objA及objA下所有的从实体
objA.Remove();
session.Commit();
}
- 只删除从实体:
删除从实体有两种方式,一种是调用从实体的objA.B_Com1cN.RemoveAt(0),一种是调用objA.B_Com1cN.Remove(b),这两种方式是等价的
using (ISession session = Session.Open())
{
objA = A_Com1cN.Finder.Find("ID = '" + objA.ID + "'", null);
B_Com1cN.EntityList bList = objA.B_Com1cN;
B_Com1cN b = bList[2];
objA.B_Com1cN.RemoveAt(0);
objA.B_Com1cN.Remove(b);
session.Commit();
}
- Q&A
- 对于一对多组合,我怎样知道删除的子对象
我们知道,对于一对多的情况,当我们对子对象调用Remove方法时,该对象会从当前的集合中移出,即子对象的集合存放的是有效的对象,是新建和修改的对象。如果我们还需要访问删除的对象,可以从当前的List的属性DelLists中找到。
- 对一对多组合的子对象作集合Clear操作,怎么没有删除
需要注意,实体的删除动作只能用Remove方法,Clear是集合本身的方法,不是持久层提供的删除自对象的方法
实体的查询
BE的查询这块主要有3个查询类,EntityDataQuery, EntityQuery和EntityViewQuery,EntityDataQuery 主要用于希望返回结果是IDataReader,DataSet,单值的情况
EntityQuery 主要用于希望返回的结果是实体,实体集合
EntityViewQuery 主要用于对视图的操作,与前2种不同的是,需要自己处理数据库的连接。
EntityDataQuery
- 创建方式:
有2种方式创建EntityDataQuery
通过Entity对象:
EntityDataQuery q = Yel_Ass1to1_A.Finder.CreateDataQuery();
通过EntityFullName:
EntityDataQuery q = newEntityDataQuery("Association.Yel_1to1Self");
EntityDataQuery q = newEntityDataQuery(Yel_1to1Self.EntityRes.BE_FullName);
- 返回DataSet:FindDataSet
全OQL方式
DataSet ds = q.FindDataSet("select ID,Name,Code from Association::Yel_1to1Self");
条件OQL方式
DataSet ds = q.FindDataSet( "ID>1");
- 返回IDataReader :FindDataReader
全OQL方式
IDataReader dr = q.FindDataReader ("select ID,Name,Code from Association::Yel_1to1Self");
条件OQL方式
IDataReader dr = q.FindDataReader ( "ID>1");
- 返回单值:
q.FindValue("select Max(ID) where id > 111");
- 分页查询
DataSet ds = q.FindDataSetByPage(1, 2, " ID > @ID and Name = @mmm order by ID");
EntityQuery
- 创建方式:
有2种方式创建EntityQuery
通过Entity对象:
EntityQuery eq = Yel_Ass1to1_A.Finder.CreateQuery();
通过EntityFullName:
EntityQuery eq = newEntityQuery ("Association.Yel_1to1Self");
EntityQuery eq = new EntityQuery(Yel_Ass1to1_A.EntityRes.BE_FullName);
另外,以强类型的方式使用Yel_Ass1toN_A.Finder,其对外接口和功能与EntityQuery类似,只是Yel_Ass1toN_A.Finder返回强类型的对象,EntityQuery返回弱类型的对象
- 返回实体:FindByID 和 Find
FindByID ,先从缓存加载对象,如果缓存没有,则从数据库加载对象
Yel_Ass1toN_A a = Yel_Ass1toN_A.Finder. FindByID (a.ID);
Yel_Ass1toN_A a = (Yel_Ass1toN_A)eq.FindByID(a.ID);
Find方式,直接从数据库加载
q.Parameters.Add(new OqlParam("TestParamBinding"));
Yel_Ass1toN_A.Finder.Find("ID = @ID”);
eq.Find("ID = @ID”);
- 返回实体集合:FindAll
全OQL方式(注意,因为此处返回的是实体的集合,所以Select要写全部的属性名称,最好不要用select * ,因为现在Select *,对*号的解析是依赖表结构而不是实体结构)
eq.FindAll("select ID,Name,Code, CreatedOn, CreatedBy, ModifiedOn, ModifiedBy, SysVersion from Association::Yel_Ass1to1_A where ID not in (select ID from Association::Yel_Ass1to1_B) order by ID");
条件OQL方式
q = new EntityQuery(Yel_Ass1to1_A.EntityRes.BE_FullName);
q.Parameters.Add(new OqlParam("TestParamBinding"));
q.FindAll("Name = @Name ");
参数的使用
查询参数有2种使用方式:
方式1:
EntityDataQuery q = Yel_Ass1to1_A.Finder.CreateDataQuery();
q.Parameters.Add(new OqlParam(1234567890));
q.Parameters.Add(new OqlParam("forTest"));
q.FindAll("ID=@ID and Name = @Name "));
方式2 :
Yel_Ass1to1_A.Finder. FindAll ("ID=@aaa and Name=@bbb", new OqlParam(1234567890), newOqlParam("forTest"))
参数绑定
目前对查询参数的绑定支持2种方式:按顺序绑定和按名称绑定,按顺序绑定要求绑定的参数和oql语句的参数个数必须一致,按名称绑定要求绑定的参数和oql语句的参数名字必须一致,下面2段代码简单示范如果使用:
按顺序绑定:此时必须有2个OqlParam
EntityQuery q = newEntityQuery(Yel_Ass1to1_A.EntityRes.BE_FullName);
q.Parameters.Add(new OqlParam("TestParamBinding"));
q.Parameters.Add(new OqlParam("TestParamBinding"));
q.FindAll("Name = @Name1 and Name like @Name2");
按名称绑定:此时必须名字都为“Name”
EntityQuery q = newEntityQuery(Yel_Ass1to1_A.EntityRes.BE_FullName);
q.Parameters.Add(new OqlParam("Name", "TestParamBinding"));
q.FindAll("Name = @Name and Name like @Name");
实体的关系查询控制
为了提高实体查询的效率,提供了类ObjectQueryOptions来控制对关系的获取。由于现在对象加载采用懒加载的方式,这种制定加载方式基本上不用了
public void Find () {
EntityQuery qo = Customer.Finder.CreateQuery();
ObjectQueryOptions options = qo.Options;
//指定在查询Customer时,同时加载关联的Address
options[“Address”].RetrieveOption
= QueryRetrieveOptions.ImmediateLoad;
IList customers = qo.FindAll();
}
弱类型实体操作
弱类型实体新建
///
/// 根据名称建实体
///
[Test]
public void TestCrtEntity()
{
DateTime dt1 = DateTime.Now;
for (int i = 0; i < 1; i++)
{
using (ISession s = Session.Open())
{
string fullname = "Association.A_Ass1to1";
Entity entity = Entity.Create(fullname, null);
entity.SetValue("Name_A_1to1", "1234567");
s.Commit();
}
}
}
弱类型实体删除
///
/// 根据名称删除实体
///
[Test]
public void TestDelEntity()
{
for (int i = 0; i < 1; i++)
{
using (ISession s = Session.Open())
{
string fullname = "Association.A_Ass1to1";
EntityQuery query = new EntityQuery(fullname);
IList list = query.FindAll();
s.Remove((IEntity)(list[0]));
s.Commit();
}
}
}
弱类型实体查询,修改
///
///查询
///
[Test]
public void FindbyFullName()
{
using (ISession s = Session.Open())
{
EntityQuery query = new EntityQuery("Association.A_Ass1to1");
//此处查询
IList list = query.FindAll("ID > '111'");
if (list != null && list.Count > 0)
{
Entity entity = list[0] asEntity;
if (entity != null)
//此处为修改
entity.SetValue("Name_A_1to1", "forTest");
s.Save(entity);
}
}
}
属性类型的操作
属性类型是一种比较特殊的对象,它本身没有ID,不单独存储,当一个实体引用一个属性类型时,它的属性可以作为实体属性的一部分,持久化时,将存储到实体的表中。模型和示例代码如下:
InheritAssValue objA = A_InheritAssValue.Create();
objA.Code_A_InheritAssValue = 1001;
//方式1:
objA.PropertyTypeA.AttriValue1 = "valueA1";
objA.PropertyTypeA.AttriValue2 = "valueA2";
//方式2:
objA.PropertyTypeA = new PropertyTypeA("valueB1", "valueB2");
//方式3:
PropertyTypeA pa = new PropertyTypeA();
pa.AttriValue1 = "valueA1";
pa.AttriValue2 = "valueA2";
objA.PropertyTypeA = pa;
实体的效验
实体的效验有两种,一种是对实体某个属性的效验,一种是对实体的效验。两种效验的机制不同和使用,说明如下:
实体某属性的效验:
在我们的IDE上,可以直接设置对实体属性的效验,,可以先建一个效验器,然后在实体的相关属性上选择这个效验器,一个实体属性可以绑定多个效验器,由设计决定,设置的界面如下图
对于实体属性的效验,我们会在生成代码时自动在属性的Set方法中插入这个效验器的效验,要说明的是,具体这个效验器逻辑怎么效验,是开发人员在生成的效验器的程序里编写,并且,这个效验器可以是有参的
///
/// Validate Logic
///
///
/// if validation success, return true, else
return false
public bool Validate(object value){
throw new Validate Exception();
// 填写你的逻辑处理
}
实体本身的效验
对于一些需要关联多个属性的统一效验,平台提供一个 OnValidate方法去实现,这个方法是自动在实体类中生成,在实体的新建和修改前会自动调用,OnValidate见下:
///
/// on Validate
///
protectedoverride void OnValidate() {
// To do ...
base.OnValidate();
}
实体效验参数
在某些情况下,可能不需要实体的效验,包括属性效验和实体本身效验,平台提供一个参数NeedValidate可以控制是否需要效验,示例代码:
A_Ass1to1 obj = Association.A_Ass1to1.Create();
//不需要效验
obj.NeedValidate = false;
平台提供的效验及顺序
平台主要提供效验主要有:
- 非空效验
- 引用对象存在性效验(包括关联实体对象,枚举对象)
- 敏感字段被引用后不让修改的效验
这些效验的顺序是:以一个对象A组合对象B再组合对象C这样的3层关系来看:
A的非空校验—》B的非空校验—》C的非空校验—》A的实体对象有效引用检查,枚举对象有效引用检查—》B的实体对象有效引用检查,枚举对象有效引用检查—》C的实体对象有效引用检查,枚举对象有效引用检查—》C的业务异常—》C 敏感字段的检查 –》B的业务异常—》B 敏感字段的检查 –》A的业务异常—》A 敏感字段的检查
实体的事件
实体内部的事件
实体的事件在子类上会自动生成OnSetDefaultValue(), OnInserting(), OnInserted(), OnUpdating(), OnUpdated(), OnDeleting(),OnDeleted(),这样子类可以处理自己要求的逻辑,类似OnValidate。处理方式类似效验:
///
/// before Insert
///
protected override void OnInserting() {
// To do ...
base.OnInserting();
}
- 一些规则
OnSetDefaultValue 方法一般是对实体的补充附值
OnInserting/ OnUpdating/ OnDeleting / OnInserted/ OnUpdated / OnDeleted 不允许对实体本身的属性再进行修改,但可以调用别的组建服务
- 事件的调用顺序说明:
对于 A 组合 B,A为主实体,以新建为例,事件依次为:
SetDefaultValue事件: A -〉B
valid 事件: B -〉A
inserting 事件: A -〉B
DB操作
inserted 事件: B -〉A
修改和删除的事件顺序也和新建一样,只不过,删除时没有设置默认值事件和valid 事件
举例如下:Yel_Com1toN_A 与Yel_Com1toN_B是一对多组合,模型如下
其中,在Yel_Com1toN_AExtend.cs和Yel_Com1toN_BExtend.cs文件关于事件处我们添加一些输出:
Yel_Com1toN_AExtend.cs:
///
/// before Insert
///
protected override void OnInserting() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_A - OnInserting " + this.ID);
base.OnInserting();
}
///
/// after Insert
///
protected override void OnInserted() {
// To do ...
base.OnInserted();
Console.WriteLine("***** Yel_Com1toN_A - OnInserted " + this.ID);
}
///
/// before Update
///
protected override void OnUpdating() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_A - OnUpdating " + this.ID);
base.OnUpdating();
}
///
/// after Update
///
protected override void OnUpdated() {
// To do ...
base.OnUpdated();
Console.WriteLine("***** Yel_Com1toN_A - OnUpdated " + this.ID);
}
///
/// before Delete
///
protected override void OnDeleting() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_A - OnDeleting " + this.ID);
base.OnDeleting();
}
///
/// after Delete
///
protected override void OnDeleted() {
// To do ...
base.OnDeleted();
Console.WriteLine("***** Yel_Com1toN_A - OnDeleted " + this.ID);
}
///
/// on Validate
///
protected override void OnValidate() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_A - OnValidate " + this.ID);
base.OnValidate();
this.SelfEntityValidator();
}
protected override void OnSetDefaultValue()
{
Console.WriteLine("***** Yel_Com1toN_A - OnSetDefaultValue " + this.ID);
base.OnSetDefaultValue();
}
Yel_Com1toN_BExtend.cs:
///
/// before Insert
///
protected override void OnInserting() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_B - OnInserting " + this.ID);
base.OnInserting();
}
///
/// after Insert
///
protected override void OnInserted() {
// To do ...
base.OnInserted();
Console.WriteLine("***** Yel_Com1toN_B - OnInserted " + this.ID);
}
///
/// before Update
///
protected override void OnUpdating() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_B - OnUpdating " + this.ID);
base.OnUpdating();
}
///
/// after Update
///
protected override void OnUpdated() {
// To do ...
base.OnUpdated();
Console.WriteLine("***** Yel_Com1toN_B - OnUpdated " + this.ID);
}
///
/// before Delete
///
protected override void OnDeleting() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_B - OnDeleting " + this.ID);
base.OnDeleting();
}
///
/// after Delete
///
protected override void OnDeleted() {
// To do ...
base.OnDeleted();
Console.WriteLine("***** Yel_Com1toN_B - OnDeleted " + this.ID);
}
///
/// on Validate
///
protected override void OnValidate() {
// To do ...
Console.WriteLine("***** Yel_Com1toN_B - OnValidate " + this.ID);
base.OnValidate();
this.SelfEntityValidator();
}
protected override void OnSetDefaultValue()
{
Console.WriteLine("***** Yel_Com1toN_B - OnSetDefaultValue " + this.ID);
base.OnSetDefaultValue();
}
测试代码如下:
[Test]
public void TestCrtDelEvent()
{
using (ISession s = Session.Open())
{
Yel_Com1toN_A a = Yel_Com1toN_A.Create();
Yel_Com1toN_B b1 = Yel_Com1toN_B.Create(a);
s.Commit();
a = Yel_Com1toN_A.Finder.Find("Id =" + a.ID);
a.Remove();
s.Commit();
}
}
输出如下:
新建时:
***** Yel_Com1toN_A - OnSetDefaultValue
***** Yel_Com1toN_B - OnSetDefaultValue
***** Yel_Com1toN_B - OnValidate
***** Yel_Com1toN_A - OnValidate
***** Yel_Com1toN_A - OnInserting
***** Yel_Com1toN_B - OnInserting
sql: INSERT INTO [Yel_Com1toN_A] ( [ModifiedBy] , [CreatedBy] , [SysVersion] , [CreatedOn] , [ModifiedOn] , [Name] , [ID] , [Code] ) VALUES (@ModifiedBy,@CreatedBy,@SysVersion,@CreatedOn,@ModifiedOn,@Name,@ID,@Code)
sql: INSERT INTO [Yel_Com1toN_B] ( [ID] , [ModifiedBy] , [SysVersion] , [Name] , [ModifiedOn] , [CreatedOn] , [Yel_Com1toN_A] , [Code] , [CreatedBy] ) VALUES (@ID,@ModifiedBy,@SysVersion,@Name,@ModifiedOn,@CreatedOn,@Yel_Com1toN_A,@Code,@CreatedBy)
***** Yel_Com1toN_B - OnInserted
***** Yel_Com1toN_A - OnInserted
删除时:
***** Yel_Com1toN_A - OnDeleting
***** Yel_Com1toN_B - OnDeleting
sql: DELETE FROM [Yel_Com1toN_B] WHERE [ID] =@ID AND [SysVersion] =@SysVersion
sql: DELETE FROM [Yel_Com1toN_A] WHERE [SysVersion] =@SysVersion AND [ID] =@ID
***** Yel_Com1toN_B - OnDeleted
***** Yel_Com1toN_A - OnDeleted
实体事件接口
对外部类,提供了事件,包括:Validate,Inserting, Inserted, Updating, Updated, Deleting, Deleted。
[Test]
public void TestGetProperty()
{
using (ISession session = Session.Open())
{
A_Ass1to1 obj = A_Ass1to1.Create();
obj.Inserting += new EventHandler(A_Ass1to1EventInserting);
Assert.IsFalse(getting, "A_Ass1to1 Inserting failed");
session.Commit();
Assert.IsTrue(getting, "A_Ass1to1 Inserting failed");
}
}
private bool getting = false;
void A_Ass1to1EventInserting(object sender, EventArgs e)
{
Assert.IsNotNull(((A_Ass1to1)sender).ID);
this.getting = true;
}
使用事件
通过事件系统来使用实体的事件,关于事件系统参见方豪的《事件系统使用指南.doc》,下面以新建为例,说明一下怎样在实体上使用
在实体的新建操作前,会调用UFSoft.UBF.Eventing.EventBroker.Publish("Inserting", this, new EventArgs());来发布一个新建前的事件,在新建后,会调用UFSoft.UBF.Eventing.EventBroker.Publish("Inserted", this, new EventArgs())来发布一个新建后事件,如果我需要使用,按下面的方式订阅这个事件:
[Test]
publicvoid TestInsertingEvent()
{
EventBroker.Subscribe("Inserting", newEventHandler(OnTestInsertingEvent));
EventBroker.Subscribe("Inserted", newEventHandler(OnTestInsertingEvent));
using (ISession s = Session.Open()){
Yel_Ass1to1_A a = Yel_Ass1to1_A.Create();
s.Commit();
}
}
privatevoid OnTestInsertingEvent(object sender, EventArgs e)
{
Console.Write("/n %%%%%%%%%%%%%%%%%%%% /n" + sender.GetType() + ((Association.Yel_Ass1to1_A)sender).SysState);
}
privatevoid OnTestInsertedEvent(object sender, EventArgs e)
{
Console.Write("/n %%%%%%%%%%%%%%%%%%%% /n" + sender.GetType() + ((Association.Yel_Ass1to1_A)sender).SysState);
}
关系
基本概念
- 属性
属性分为原子属性和关联属性两种,原子属性的值是一个原子,通常对应为一个简单类型,关联属性对应一个实体间的关系,这个关系通常指向另一个实体。
- 关联关系
实体间关系的一种,表示实体间的联系不是很密切,是弱类型,如常见的人员和部门的关系,客户和地址的关系
- 组合关系
实体间关系的一种,表示实体间很密切的关系,两实体一般同一个生命周期,是强类型,如常见的订单和订单分录之间的关系
实体属性的加载规则
考虑到对象的完整性,对一个实体加载,系统默认加载全部的原子属性,而关联属性的加载规则遵循下一条“关联属性的加载规则”。
关联属性的加载规则
关联属性的加载取决于它所对应的关系类型和用户的加载配置情况。
- 对于关联关系(弱类型),用户可以通过UFSoft.UBF.PL. ObjectQueryOptions类选择加载方式是懒加载还是立即加载,如果用户没有在ObjectQueryOptions中定义加载了类型,系统默认处理为懒加载。
- 对于组合关系(强关系),用户可以通过UFSoft.UBF.PL. ObjectQueryOptions类选择加载方式是懒加载还是立即加载,如果用户没有在ObjectQueryOptions中定义加载了类型,2006年4月后的版本,系统默认处理为懒加载,注意,这块加载方式的改变,老版本是立即加载,新版本为懒加载
关系的多重性
实体间的关系按照两端多重性的划分,分为一对一,一对多和多对一和多对多
关系的级联处理
- 级联删除
- 注意设计时要先选级联删除的开关为true,再设计级联删除规则 ,级联删除规则见下表
- 当级联规则为NoAction的情况,增加一个是否启用级联效验的标志,只有当这个标志为true,才进行NoAction下的引用效验,而这个是否启用级联效验的标志,也只在规则为NoAction时有效,其他规则下都无效,即
及联删除 : 是bool型,表示在删除时关联关系是不是要做及联处理
及联删除规则: 只有当及联删除标志为true时,这个规则才起作用,有3种情况,cascade,noaction,setnull
是否启用及联效验:只有当及联删除为true,及联删除规则为noaction时才有效,表示这个关联是否进行被引用不能删除的检查
Cascade | NoAction | SetNull | |
A和B为一对一或多对一(以A为操作实体) | 删除A,B | 只删除A,不处理B | 删除A,不处理B |
A和B为一对多(以A为操作实体) | 删除A,B | 如果B中引用了A,则两个都不能删,抛异常,否则,删A,不处理B | 删除A,B对象对A的引用置空 |
A和B为一对一或多对一(以B为操作实体) | 删除B(不提供级联删除A的操作,如果有具体的业务需要再考虑) | 如果A中引用B,则两个都不删,抛异常,否则,删除B | 删除B,A对象对B的引用置空 |
- 级联修改(仅指敏感字段的控制)
应用场景是A实体关联B实体,如果B实体上有一旦使用不可修改的属性, 那么,当修改这个属性时,如果发现有A引用了B,则抛异常“XXX已被使用,不能修改”
要达到上面的需求,需要使用者设置2个地方:
- 一个是IDE设计器的实体属性上,即对B实体的属性“一旦使用不可修改”设置为true,见下图,注意,平台只处理原生类型属性,即对于属性类型,集合类型不处理
- 在实体A和实体B的关联线上“检查修改”设置为true
多语言的处理
基本概念
实体的属性在IDE中可以设计为多语言的字段,表示这个字段可以存储不同语种的信息,如订单上的一个地址设计为多语言的字段,那么,可以在这个字段上同时存储中文信息和英文信息等等多种语言的信息
在表的设计上,如果实体中有多语言的字段,那么,这个实体就会多产生一张表名后缀为“_Trl”的多语言的表,如A_MultiLanguages为含多语言字段的实体,它产生的表是T_A_MultiLanguages和T_A_MultiLanguages_Trl,多语言的信息只存在多语言表T_A_MultiLanguages_Trl中,而主表T_A_MultiLanguages不包含多语言的字段
使用方式
下面是示例代码,假设语种信息为3种:“CN”,“EN”,“F”,当前默认语种为“CN”,另外,实体 A_MultiLanguages含有3个多语言的字段Name_MultiL_L1,Name_MultiL_L2,Name_MultiL_L3
A_MultiLanguages obj;
MultiLangDataDict langs = newMultiLangDataDict();
using (ISession session = Session.Open())
{
//此处是我写测试用例临时往 Context上赋默认语种为“CN”,实际上这个值是用户//login后ui会处理的,是不能像下面这样赋值的
UFSoft.UBF.Util.Context.ContextManager.Context["CultureName"] = "CN";
obj = A_MultiLanguages.Create();
//这是常规写法,直接往属性里面赋值,此时系统认为是赋默认语种的信息
obj.Name_MultiL_L2 = "Name_MultiL_L2";
obj.Name_MultiL_L3 = "Name_MultiL_L3";
//下面代码显示怎样往一个属性里面一次送多个语种的信息,注意方法//SetMultiLangPropDict,第一个参数为属性名,第二个为多语言的数据
//一般是不会使用这种方式赋多语言信息,特殊情况下使用
langs.AddNewData("CN", "中国");
langs.AddNewData("EN", "US");
langs.AddNewData("F", "FFF");
obj.SetMultiLangPropDict("Name_MultiL_L1", langs);
session.Commit();
}
EntityViewQuery
EntityViewQuery是提供类似数据库View查询功能的类,应用于比较复杂的查询,如报表,对外主要提供如下的方法:
- 查询返回DataSet
public DataSet ExecuteDataSet(ObjectQuery query, params UFSoft.UBF.PL.OqlParam[] oqlParameters)
public DataSet ExecuteDataSet(string TempVariable, ObjectQuery query, params UFSoft.UBF.PL.OqlParam[] oqlParameters)
- 查询返回DataReader
public IDataReader ExecuteReader(ObjectQuery query, params UFSoft.UBF.PL.OqlParam[] oqlParameters)
public IDataReader ExecuteReader(string TempVariable, ObjectQuery query, params UFSoft.UBF.PL.OqlParam[] oqlParameters)
- ExecuteNonQuery,返回受影响的行数
public int ExecuteNonQuery(string TempVariable, ObjectQuery query, params UFSoft.UBF.PL.OqlParam[] oqlParameters)
- 更新DataSet
public int UpdateDataSet(DataSet ds)
- 构造ObjectQuery
public ObjectQuery CreateQuery(string oql)
- 往临时表插入记录
public int ExecuteAppendNonQuery(string TempVariable,ObjectQuery insertQuery,params UFSoft.UBF.PL.OqlParam[] oqlParameters)
示例代码如下,注意使用者要自己维护数据库连接的开关状态,即查询前记得开连接,查询后记得关闭数据库连接:
private static string slotName = "BaseConnection";
///
/// 测试返回IDataReader的情况
///
[Test]
publicvoid TestViewQueryReader()
{
//using (EntityViewQuery q = new EntityViewQuery())
{
IDbConnection conn = DatabaseManager.GetCurrentConnection();
DatabaseManager.CurrentConnection = conn;
conn.Open();
EntityViewQuery q = newEntityViewQuery();
ObjectQuery query = q.CreateQuery("select * from Association::A_Ass1to1");
//定义一个临时表aaaView
IDataReader reader = q.ExecuteReader("aaaView", query, null);
reader.Close();
using (reader = q.ExecuteReader(query, null))
{
}
reader.Close();
EntityViewQuery q1 = newEntityViewQuery();
//从临时表中查询记录并带查询的参数
ObjectQuery query1 = q.CreateQuery("select * from aaaView where id > @aaa");
IDataReader reader2 = q.ExecuteReader(query1, new OqlParam(11));
conn.Close();
}
}
///
/// 测试返回DataSet的情况
///
[Test]
publicvoid TestViewQueryDataSet()
{
//using (EntityViewQuery q = new EntityViewQuery())
{
IDbConnection conn = DatabaseManager.GetCurrentConnection();
DatabaseManager.CurrentConnection = conn;
conn.Open();
EntityViewQuery q = newEntityViewQuery();
DataSet ds0 = q.ExecuteDataSet("aaaView", q.CreateQuery("select * from Association::A_Ass1to1"),null);
DataSet ds1 = q.ExecuteDataSet("bbbview", q.CreateQuery("select * from aaaView"),null);
DataSet ds2 = q.ExecuteDataSet(q.CreateQuery("select * from bbbview where id > @ttt"),new OqlParam(1));
conn.Close();
}
}
///
/// 测试ExecuteNonQuery
///
[Test]
publicvoid TestQueryNoneQuery()
{
IDbConnection conn = DatabaseManager.GetCurrentConnection();
DatabaseManager.CurrentConnection = conn;
conn.Open();
EntityViewQuery q = new EntityViewQuery();
int i = q.ExecuteNonQuery("aaaView", q.CreateQuery("select * from Association::A_Ass1to1"), null);
Assert.IsTrue(i > 0);
conn.Close();
}
///
/// 测试更新?
///
[Test]
publicvoid TestUpdateDataSet()
{
IDbConnection conn = DatabaseManager.GetCurrentConnection();
DatabaseManager.CurrentConnection = conn;
conn.Open();
EntityViewQuery q = new EntityViewQuery();
DataSet ds0 = q.ExecuteDataSet(q.CreateQuery("select * from Association::A_Ass1to1 where id > @ttt"), new OqlParam(1));
Assert.AreEqual("YELIN", ds0.Tables[0].Rows[1][4]);
DataRow row = ds0.Tables[0].Rows[1];
row[4] = "yel";
q.UpdateDataSet(ds0);
ds0 = q.ExecuteDataSet(q.CreateQuery("select * from Association::A_Ass1to1 where id > @ttt"), new OqlParam(1));
Assert.AreEqual("yel", ds0.Tables[0].Rows[1][4]);
row = ds0.Tables[0].Rows[1];
row[4] = "YELIN";
q.UpdateDataSet(ds0);
ds0 = q.ExecuteDataSet(q.CreateQuery("select * from Association::A_Ass1to
实体开发框架
本文2024-08-20 17:08:44发表“u9cloud知识”栏目。
本文链接:https://wenku.my7c.com/article/yonyou-u9cloud-1162.html