Condividi tramite


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;
        }
       
    }
}