Gerarchie composizionali
WCF RIA Services consente di creare la logica dell'applicazione per le classi di dati che appartengono a gerarchie composizionali contenenti classi associate da relazioni di tipo "ha" nelle quali l'oggetto contenitore (l'intero oggetto o l'oggetto padre) controlla la creazione e la durata dell'oggetto contenuto (l'oggetto parziale o l'oggetto discendente). Ad esempio, l'entità SalesOrderHeader
dispone di un'entità SalesOrderDetail
perché i dettagli relativi a un ordine esistono solo come parte dell'ordine. Per chiarire, è possibile che la composizione delle classi sia in contrasto con la sottotipizzazione delle classi, ossia la creazione di un tipo più specifico (una macchina) aggiungendo i dettagli a un tipo più generale (un veicolo). Ciò comporta una gerarchia dell'ereditarietà nella quale la classe dettagliata (derivata) può ancora essere trattata come tipo generale (base) perché, per utilizzare l'esempio, una macchina "è (ancora) un" veicolo.
Dopo avere definito la relazione composizionale tra le classi pertinenti, è possibile eseguire operazioni di modifica dei dati sulle entità che vengono considerate come una sola unità anziché come entità separate. In questo modo, la logica di livello intermedio risulta semplificata perché è possibile scrivere la logica dell'applicazione per l'intera gerarchia anziché suddividerla per applicarla a ogni entità e tentare di coordinare tale logica suddivisa durante le operazioni sui dati.
Informazioni sulle gerarchie composizionali
In una gerarchia di entità, un'entità viene definita l'entità padre e le altre entità correlate vengono definite entità discendenti. L'entità padre è la classe che rappresenta i dati, l'unica radice per i dati delle entità discendenti. Ad esempio, l'entità SalesOrderHeader
è l'entità padre e SalesOrderDetail
è un'entità discendente. Un record dell'entità SalesOrderHeader
può essere collegato a diversi record dell'entità SalesOrderDetail
.
Le classi di dati che fanno parte di una relazione gerarchica in genere hanno le caratteristiche seguenti:
La relazione tra le entità può essere rappresentata come una struttura ad albero con le entità discendenti connesse a una sola entità padre. Le entità discendenti possono estendersi per un qualsiasi numero di livelli.
La durata di un'entità discendente è compresa nella durata dell'entità padre.
L'entità discendente non ha un'identità significativa all'esterno del contesto dell'entità padre.
Le operazioni di dati sulle entità richiedono che le entità vengano trattate come una sola unità. Ad esempio, l'aggiunta, l'eliminazione o l'aggiornamento di un record nell'entità discendente richiede una modifica corrispondente nell'entità padre.
Definizione di una relazione composizionale
Una relazione composizionale tra entità si definisce applicando l'attributo CompositionAttribute alla proprietà che rappresenta l'associazione tra le entità. Nell'esempio seguente viene illustrato come definire una relazione composizionale tra SalesOrderHeader
e SalesOrderDetail
tramite una classe di metadati. L'attributo CompositionAttribute si trova nello spazio dei nomi System.ComponentModel.DataAnnotations. È necessario fare riferimento a tale spazio dei nomi tramite l'istruzione using o Imports per applicare l'attributo come viene mostrato nel codice seguente.
<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;
}
}
Quando si applica l'attributo CompositionAttribute a una proprietà, i dati dall'entità discendente non vengono recuperati automaticamente con l'entità padre. Per includere l'entità discendente nei risultati della query, è necessario applicare l'attributo IncludeAttribute alla proprietà che rappresenta l'entità discendente e includere l'entità discendente nel metodo della query. L'esempio alla fine della prossima sezione mostra come includere l'entità discendente nel metodo della query.
Operazioni del servizio del dominio con gerarchia composizionale
Quando si definisce una gerarchia composizionale, è necessario modificare la modalità per interagire con le entità padre e discendenti. La logica che si include nei servizi del dominio deve tener conto del collegamento tra le entità. In genere, la logica per la gerarchia si definisce tramite i metodi del servizio del dominio per l'entità padre. Nelle operazioni del servizio del dominio per l'entità padre, si elaborano le modifiche dell'entità padre e qualsiasi modifica delle entità discendenti.
Le seguenti regole si applicano alle operazioni del servizio del dominio per le entità con relazioni composizionali:
I metodi della query per entità padre o discendenti sono consentiti, tuttavia si consiglia di recuperare le entità discendenti nel contesto dell'entità padre. Viene generata un'eccezione se si modifica un'entità discendente che è stata caricata senza l'entità padre.
È possibile aggiungere le operazioni di modifica dei dati alle entità discendenti, ma le operazioni consentite su un'entità discendente vengono influenzate dalle operazioni consentite sull'entità padre.
Se è consentito aggiornare per l'entità padre, è consentito aggiornare, inserire ed eliminare per l'entità discendente.
Se un'entità padre dispone di un metodo di aggiornamento denominato, tutti i discendenti devono disporre dell'aggiornamento abilitato.
Se è consentito inserire o eliminare per l'entità padre, è consentita l'operazione corrispondente per l'entità discendente in modo ricorsivo.
All'interno del progetto client vengono applicate le regole seguenti per l'utilizzo delle entità che dispongono di relazioni composizionali:
Quando un'entità discendente contiene una modifica, la notifica della modifica viene propagata fino all'entità padre. La proprietà HasChanges dell'entità padre è impostata su true.
Quando viene modificata un'entità padre, tutte le relative entità discendenti (anche quelle non modificate) vengono incluse nel set di modifiche.
Un elemento EntitySet pubblico nel contesto del dominio non viene generato nel client per le entità discendenti. È necessario accedere all'entità discendente tramite l'entità padre.
Un'entità discendente può essere definita con più predecessori allo stesso livello, ma è necessario assicurarsi che sia stata caricata all'interno del contesto di un solo predecessore.
Le operazioni di modifica dei dati vengono eseguite sulla base delle regole seguenti:
Un'operazione di aggiornamento, inserimento o eliminazione viene eseguita prima sull'entità padre prima di eseguire in modo ricorsivo qualsiasi operazione di modifica dei dati sulle entità discendenti.
Se l'operazione sui dati richiesta non è presente in un'entità discendente, l'esecuzione ricorsiva viene arrestata.
In caso di aggiornamento di un'entità padre, l'ordine di esecuzione delle operazioni dei dati dei discendenti non è specificato.
Nell'esempio seguente vengono mostrati i metodi per l'esecuzione di query, l'aggiornamento e l'eliminazione dell'entità SalesOrderHeader
. I metodi includono la logica per elaborare le modifiche nelle entità discendenti.
<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;
}
}
}