复合层次结构
利用 WCF RIA Services,您可以为属于复合层次结构的数据类创建应用程序逻辑,这些层次结构包含由“具有”关系关联的类,其中包含对象(整体或父)控制着被包含对象(部分或子代)的创建和生命周期。例如,SalesOrderHeader
实体具有 SalesOrderDetail
实体,因为有关订单的详细信息只作为订单的一部分存在。为了阐明这一点,可以对照类的子类型化来看类的组件:子类型化包括通过给比较宽泛的类型(交通工具)添加详细信息来创建比较具体的类型(小轿车)。这样就会导致在继承层次结构中详细(派生)类仍可被视为泛型(基)类,因为从这个例子来看,小轿车“(仍然)是”是交通工具。
在定义了相关类之间的复合关系之后,可以针对作为单个单元(而不是需要作为多个单独实体)的多个实体来执行数据修改操作。这会简化中间层逻辑,因为您可以为整个层次结构编写应用程序逻辑,而不是需要将该逻辑拆分,以应用于每个实体并尝试在数据操作期间协调该拆分逻辑。
理解复合层次结构
在实体的层次结构中,一个实体被称为父实体,其他相关实体被称为子代实体。父实体是表示数据的类,它是子代实体中数据的单个根。例如,SalesOrderHeader
实体是父实体,SalesOrderDetail
是子代实体。SalesOrderHeader
实体中的单个记录可以链接到 SalesOrderDetail
实体中的几个记录。
数据类是层次结构关系的一部分,通常具有以下特征:
实体之间的关系可以表示为树状结构,其中子代实体连接到单个父实体。子代实体可以扩展为任意数目的级别。
子代实体的生存期包含在父实体的生存期内。
子代实体在父实体的上下文外没有有意义的标识。
对实体的数据操作要求将这些实体作为单个单元处理。例如,在子代实体中添加、删除或更新记录要求在父实体中做相应更改。
定义复合关系
通过将 CompositionAttribute 特性应用到表示实体之间的关联的属性,定义实体之间的复合关系。下面的示例显示如何通过使用元数据类来定义 SalesOrderHeader
和 SalesOrderDetail
之间的复合关系。CompositionAttribute 特性位于 System.ComponentModel.DataAnnotations 命名空间中。您必须使用 using 或 Imports 语句来引用该命名空间,以应用该特性,如以下代码中所示:
<MetadataTypeAttribute(GetType(SalesOrderHeader.SalesOrderHeaderMetadata))> _
Partial Public Class SalesOrderHeader
Friend NotInheritable Class SalesOrderHeaderMetadata
Private Sub New()
MyBase.New
End Sub
<Include()> _
<Composition()> _
Public SalesOrderDetails As EntityCollection(Of SalesOrderDetail)
End Class
End Class
[MetadataTypeAttribute(typeof(SalesOrderHeader.SalesOrderHeaderMetadata))]
public partial class SalesOrderHeader
{
internal sealed class SalesOrderHeaderMetadata
{
private SalesOrderHeaderMetadata()
{
}
[Include]
[Composition]
public EntitySet<SalesOrderDetail> SalesOrderDetails;
}
}
将 CompositionAttribute 特性应用到某属性时,不自动使用父实体检索子代实体的数据。若要在查询结果中包含子代实体,必须将 IncludeAttribute 特性应用到表示子代实体的属性并在查询方法中包含该子代实体。下一节末尾的示例说明如何在查询方法中包含子代实体。
具有复合层次结构的域服务操作
定义复合层次结构时,必须更改与父实体和子代实体交互的方式。在域服务中包含的逻辑必须考虑实体之间的链接。通常,通过父实体的域服务方法定义层次结构的逻辑。在父实体的域服务操作中,您处理对父实体的修改和对子代实体的任何修改。
下列规则适用于具有复合关系的实体的域服务操作:
允许有父实体和子代实体的查询方法,但是,建议您在父实体的上下文中检索子代实体。如果修改已加载的没有父实体的子代实体,将引发异常。
可以将数据修改操作添加到子代实体,但是允许对子代实体的操作受允许对父实体的操作影响。
如果对父实体允许更新,则允许对子代实体进行更新、插入和删除。
如果父实体具有已命名的更新方法,则所有子代必须启用更新。
如果对父实体允许插入或删除,则允许对子代实体递归进行相应的操作。
在客户端项目中,以下规则适用于具有复合关系的实体:
子代实体包含更改时,将该更改的通知向上传播到父实体。将父实体的 HasChanges 属性设置为 true。
修改父实体时,它的所有子代实体(甚至未更改的那些子代)都将包括在更改集中。
不在子代实体的客户端生成域上下文中的公共 EntitySet。必须通过父实体访问子代实体。
可以使用同一级别的多个上级定义子代实体,但是必须确保只在一个上级的上下文中加载它。
根据以下规则执行数据更改操作:
首先执行对父实体的更新、插入或删除操作,然后递归执行对子代实体的任何数据修改操作。
如果在子代实体中没有提供所需的数据操作,将停止执行递归。
更新父实体时,不指定对子代的数据操作的执行顺序。
下面的示例显示用于查询、更新和删除 SalesOrderHeader
实体的方法。这些方法包括处理子代实体中的更改的逻辑。
<EnableClientAccess()> _
Public Class OrderDomainService
Inherits LinqToEntitiesDomainService(Of AdventureWorksLT_DataEntities)
Public Function GetSalesOrders() As IQueryable(Of SalesOrderHeader)
Return Me.ObjectContext.SalesOrderHeaders.Include("SalesOrderDetails")
End Function
Public Sub UpdateSalesOrder(ByVal currentSalesOrderHeader As SalesOrderHeader)
Dim originalOrder As SalesOrderHeader = Me.ChangeSet.GetOriginal(currentSalesOrderHeader)
If (currentSalesOrderHeader.EntityState = EntityState.Detached) Then
If (IsNothing(originalOrder)) Then
Me.ObjectContext.Attach(currentSalesOrderHeader)
Else
Me.ObjectContext.AttachAsModified(currentSalesOrderHeader, Me.ChangeSet.GetOriginal(currentSalesOrderHeader))
End If
End If
For Each detail As SalesOrderDetail In Me.ChangeSet.GetAssociatedChanges(currentSalesOrderHeader, Function(o) o.SalesOrderDetails)
Dim op As ChangeOperation = Me.ChangeSet.GetChangeOperation(detail)
Select Case op
Case ChangeOperation.Insert
If ((detail.EntityState = EntityState.Added) _
= False) Then
If ((detail.EntityState = EntityState.Detached) _
= False) Then
Me.ObjectContext.ObjectStateManager.ChangeObjectState(detail, EntityState.Added)
Else
Me.ObjectContext.AddToSalesOrderDetails(detail)
End If
End If
Case ChangeOperation.Update
Me.ObjectContext.AttachAsModified(detail, Me.ChangeSet.GetOriginal(detail))
Case ChangeOperation.Delete
If (detail.EntityState = EntityState.Detached) Then
Me.ObjectContext.Attach(detail)
End If
Me.ObjectContext.DeleteObject(detail)
End Select
Next
End Sub
Public Sub DeleteSalesOrder(ByVal salesOrderHeader As SalesOrderHeader)
If (salesOrderHeader.EntityState = EntityState.Detached) Then
Me.ObjectContext.Attach(salesOrderHeader)
End If
Select Case salesOrderHeader.Status
Case 1 ' in process
Me.ObjectContext.DeleteObject(salesOrderHeader)
Case 2, 3, 4 ' approved, backordered, rejected
salesOrderHeader.Status = 6
Case 5 ' shipped
Throw New ValidationException("The order has been shipped and cannot be deleted.")
End Select
End Sub
End Class
[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;
}
}
}