层的组成
WCF RIA Services允许我们为具有层次概念的数据类创建应用逻辑,例如SalesOrderHeader实体和SalesOrderDetail实体。这样相关实体就组成了所谓的层次。定义了类之间的组成关系后,就可以像操作一个单一个体一样来操作对实体的数据修改,而不是像操作独立实体那样。这就会简化中间层的逻辑,因为我们可以对整个实体层来写应用逻辑,而不是把逻辑拆分对应每个实体并在数据操作时企图协调这些拆分的逻辑。
了解层的概念
在实体的层级概念中,一个实体被称为父实体,其他关联的实体被称为子实体。父实体是表示数据的类,是那些子实体数据的的根。例如,SalesOrderHeader实体是父实体,SalesOrderDetail是子实体。SalesOrderHeader实体内的一个记录可以和SalesOrderDetail实体内的多个记录连接起来。
作为层次关系一部分的数据类通常具有如下特征:
- 这些实体间的关系可以表示为一个有子实体和一个父实体的树型结构。子实体可以扩展为任何数量的级别。
- 子实体的生命周期是包含在父实体的生命周期内。
- 子实体如果离开了父实体的上下文,就没有了有意义的身份。
- 需要把实体看做单一个体来对实体进行数据操作。例如,添加、删除、或更新子实体内的一个记录,需要在父实体内也有相应的改动。
定义一个组成关系
要在实体间定义层次关系,可以对在实体间表示关联的成员属性应用CompositionAttribut属性。下面的示例展示了如何通过元数据类来在SalesOrderHeader和SalesOrderDetail之间定义层次关系。CompositionAttribute属性在System.Web.Ria.Data命名空间内。需要用using来引用这个命名空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
[MetadataTypeAttribute( typeof (SalesOrderHeader.SalesOrderHeaderMetadata))] public partial class SalesOrderHeader { internal sealed class SalesOrderHeaderMetadata { private SalesOrderHeaderMetadata() { } [Include] [Composition] public EntitySet<SALESORDERDETAIL> SalesOrderDetails; } } |
当对成员属性应用CompositionAttribute属性时,子实体内的数据不能从父实体自动寻回。为了在查询结果中包含子实体,应该对表示子实体的成员属性应用IncludeAttribute属性,并在查询方法中包括子实体。可以参考最后的例子。
Domain Service操作组合的层次关系
当定义了组合层次后,需要改变父实体和子实体交互的方式。包含在Domain Service中的逻辑必须解释实体间的联系。通常,通过对父实体的Domain Service方法来定义层次的逻辑。在对父实体的Domain Service操作里,处理对父实体和子实体的更改。
下面的规则应用于Domain Service操作来操作有层次关系的实体。
- 允许对父和子实体使用查询方法,但推荐在父实体的上下文内查询子实体。如果修改没有通过父实体装载的子实体会抛出一个异常
- 可以添加对子实体的数据修改操作,但这些操作可以被父实体内的操作影响。
如果允许对父实体更改,那么更改、插入、和删除操作被允许在子实体上。
如果父实体有一个命名的更新named updated方法,那么所有子实体都必须允许更新操作。
如果在父实体允许插入、删除,那么在子实体也应该允许对应的操纵。
在客户端,对有层次关系的实体应用下面的规则。
- 当子实体包含一个改动,这个改动要通知给父实体。父实体上的HasChanges应设置为true。
- 当一个父实体被改动,所有的子实体(包括没有改动的子实体)都被包括在改动集合中。
- 在客户端的子实体中不会在Domain上下文中生成public的EntitySet,必须通过父实体来访问子实体。
- 一个子实体可以在相同的级别上有多个始祖,但必须确认它只能在一个始祖的上下文中装载。
数据更改的操作按以下的规则运行。
- 在递归执行任何子实体上的数据修改操作之前,更新、插入、或删除首先在父实体上执行。
- 如果需要的数据操作没有出现在子实体中,那递归执行会停止。
- 当更新一个父实体时,没有指定子实体数据操作的执行顺序。
下面的示例展示了SalesOrderHeader实体的查询、更改、删除方法。这些方法包含了在子实体内的处理改变的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
[EnableClientAccess()] public class OrderDomainService : LinqToEntitiesDomainService<ADVENTUREWORKSLT_DATAENTITIES> { public IQueryable<SALESORDERHEADER> GetSalesOrders() { return this .ObjectContext.SalesOrderHeaders.Include( "SalesOrderDetails" ); } public void UpdateSalesOrder(SalesOrderHeader currentSalesOrderHeader) { SalesOrderHeader originalOrder = this .ChangeSet.GetOriginal(currentSalesOrderHeader); if ((currentSalesOrderHeader.EntityState == EntityState.Detached)) { if (originalOrder != null ) { this .ObjectContext.AttachAsModified(currentSalesOrderHeader, this .ChangeSet.GetOriginal(currentSalesOrderHeader)); } else { this .ObjectContext.Attach(currentSalesOrderHeader); } } foreach (SalesOrderDetail detail in this .ChangeSet.GetAssociatedChanges(currentSalesOrderHeader, o => o.SalesOrderDetails)) { ChangeOperation op = this .ChangeSet.GetChangeOperation(detail); switch (op) { case ChangeOperation.Insert: if ((detail.EntityState != EntityState.Added)) { if ((detail.EntityState != EntityState.Detached)) { this .ObjectContext.ObjectStateManager.ChangeObjectState(detail, EntityState.Added); } else { this .ObjectContext.AddToSalesOrderDetails(detail); } } break ; case ChangeOperation.Update: this .ObjectContext.AttachAsModified(detail, this .ChangeSet.GetOriginal(detail)); break ; case ChangeOperation.Delete: if (detail.EntityState == EntityState.Detached) { this .ObjectContext.Attach(detail); } this .ObjectContext.DeleteObject(detail); break ; case ChangeOperation.None: break ; default : break ; } } } public void DeleteSalesOrder(SalesOrderHeader salesOrderHeader) { if ((salesOrderHeader.EntityState == EntityState.Detached)) { this .ObjectContext.Attach(salesOrderHeader); } switch (salesOrderHeader.Status) { case 1: // in process this .ObjectContext.DeleteObject(salesOrderHeader); break ; case 2: // approved case 3: // backordered case 4: // rejected salesOrderHeader.Status = 6; break ; case 5: // shipped throw new ValidationException( "The order has been shipped and cannot be deleted." ); default : break ; } } } |