複合階層
WCF RIA Services 可讓您為屬於複合階層的資料類別建立應用程式邏輯,其中包含的類別以 "has a" 關聯性相關聯,包含的物件 (整個物件或父代) 控制被包含物件 (部分物件或子代) 的建立和存留期。例如 SalesOrderHeader
實體有 SalesOrderDetail
實體,因為關於訂單的詳細資料只當做訂單的一部分存在。為釐清說明,類別組成可對照類別子型別,這包含藉由將詳細資料加入至更一般性型別 (運輸工具),來建立更特定的型別 (車)。這會導致詳細 (衍生) 類別仍可以視為一般 (基底) 型別的繼承階層,因為沿用此範例,車 "is (still) a" (仍然是) 運輸工具。
在定義相關類別之間的複合關聯性之後,您可以在將這些類別視為一個單位 (而不需要以個別實體方式處理) 的實體上執行資料修改作業。這樣會簡化中介層邏輯,因為您可以撰寫整個階層的應用程式邏輯,而不用分割這個邏輯以套用至每一個實體,然後嘗試在資料作業期間協調該分割邏輯。
了解複合階層
在實體的階層中,一個實體稱為父實體,其他相關實體則稱為子代實體。父實體是一種代表資料的類別,它是子系實體中資料的唯一根。例如,SalesOrderHeader
實體是父實體,SalesOrderDetail
則是子系實體。SalesOrderHeader
實體中的單一記錄可以連結至 SalesOrderDetail
實體中的數個記錄。
屬於階層式關聯性的資料類別通常有以下特性:
實體之間的關聯性可以表示成單一父實體與子系實體相連接的樹狀結構。子系實體可以擴充為任何數目的層級。
子系實體的存留期是包含在父實體的存留期之內。
子系實體在父實體的內容之外不具有意義的識別。
實體上的資料作業實體必須被視為一個單位。例如,在子系實體中加入、刪除或更新記錄,在父實體中就需要有對應的變更。
定義複合關聯性
在代表實體之間關聯的屬性 (Property) 中套用 CompositionAttribute 屬性 (Attribute),即可定義實體之間的複合關聯性。下列範例示範如何使用中繼資料類別來定義 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 屬性 (Attribute) 套用至屬性 (Property) 時,並不會透過父實體自動擷取子系實體的資料。若要在查詢結果中包含子系實體,您必須將 IncludeAttribute 屬性 (Attribute) 套用至代表子系實體的屬性 (Property),並在查詢方法中包含子系實體。下一節結尾處的範例將示範如何在查詢方法中包含子系實體。
網域服務作業與複合階層
當您定義複合階層時,必須變更與父實體和子系實體進行互動的方式。包含在網域服務中的邏輯必須負責實體之間的連結。一般來說,您會透過父實體的網域服務方法定義階層的邏輯。在父實體的網域服務作業中,您會處理對父實體所做的修改和對子系實體所做的任何修改。
下列規則適用於有複合關聯性之實體的網域服務作業:
允許父實體或子系實體的查詢方法,但是建議您從父實體的內容擷取子系實體。如果修改已載入的子系實體但沒有修改父實體,就會擲回例外狀況。
資料修改作業可以加入至子系實體,但子系實體上允許的作業會受到父實體影響。
如果父實體上允許更新作業,則子系實體上就會允許更新、插入和刪除作業。
如果父實體有具名更新方法,則所有子系都必須啟用更新。
如果父實體上允許插入或刪除作業,則子系上就會以遞迴方式允許對應的作業。
在用戶端專案中,下列規則適用於使用有複合關聯性的實體:
當子系實體有變更時,變更通知會往上傳播到父實體。父實體上的 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;
}
}
}