Hiérarchies de composition

Les Services RIA WCF vous permettent de créer la logique d'application pour les classes de données qui appartiennent à des hiérarchies de composition contenant des classes associées par des relations de type « possède un » dans lesquelles l'objet contenant (l'objet entier ou le parent) contrôle la création et la durée de vie de l'objet contenu (l'objet partiel ou le descendant). Par exemple, l'entité SalesOrderHeader possède une entité SalesOrderDetail car les détails concernant une commande n'existent que dans le cadre de cette commande. Pour clarifier, la composition des classes peut être comparée au sous-typage de classes, lequel correspond à la création d'un type plus spécifique (une voiture) grâce à l'ajout de détails à un type plus général (un véhicule). Cela se traduit par une hiérarchie d'héritage dans laquelle la classe détaillée (dérivée) peut toujours être traitée comme type général (de base) car, pour reprendre notre exemple, une voiture « est (toujours) un » véhicule.

Après avoir défini la relation de composition entre les classes appropriées, vous pouvez exécuter des opérations de modification de données sur les entités qui les traitent en tant qu'unité unique au lieu d'avoir à les gérer en tant qu'entités distinctes. Cela simplifie la logique de couche intermédiaire car vous pouvez écrire la logique d'application pour l'intégralité de la hiérarchie plutôt que de fractionner cette logique à appliquer à chaque entité, puis essayer de coordonner cette logique fractionnée durant les opérations de données.

Présentation des hiérarchies de composition

Dans une hiérarchie d'entités, une entité est appelée l'entité parent et les autres entités associées sont appelées les entités descendantes. L'entité parent est la classe qui représente les données et qui est la racine unique pour les données des entités descendantes. Par exemple, l'entité SalesOrderHeader est l'entité parent, et SalesOrderDetail est une entité descendante. Un enregistrement unique dans l'entité SalesOrderHeader peut être lié à plusieurs enregistrements dans l'entité SalesOrderDetail.

Les classes de données qui font partie d'une relation hiérarchique ont généralement les caractéristiques suivantes :

  • Les relations entre les entités peuvent être représentées sous la forme d'un arbre avec des entités descendantes connectées à une entité parent unique. Les entités descendantes peuvent s'étendre à un nombre quelconque de niveaux.

  • La durée de vie d'une entité descendante est comprise dans la durée de vie de l'entité parent.

  • L'entité descendante n'a pas d'identité significative en dehors du contexte de l'entité parent.

  • Les opérations de données sur les entités exigent que les entités soient traitées en tant qu'unité unique. Par exemple, l'ajout, la suppression ou la mise à jour d'un enregistrement dans l'entité descendante exige une modification correspondante dans l'entité parent.

Définition d'une relation de composition

Vous définissez une relation de composition entre des entités en appliquant l'attribut CompositionAttribute à la propriété qui représente l'association entre les entités. L'exemple suivant indique comment définir une relation de composition entre SalesOrderHeader et SalesOrderDetail à l'aide d'une classe de métadonnées. L'attribut CompositionAttribute se trouve dans l'espace de noms System.ComponentModel.DataAnnotations. Vous devez référencer cet espace de noms en utilisant l'instruction using ou Imports pour appliquer l'attribut comme indiqué dans le code suivant.

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

    }
}

Quand vous appliquez l'attribut CompositionAttribute à une propriété, les données de l'entité descendante ne sont pas récupérées automatiquement avec l'entité parent. Pour inclure l'entité descendante dans les résultats de la requête, vous devez appliquer l'attribut IncludeAttribute à la propriété qui représente l'entité descendante et inclure l'entité descendante dans la méthode de requête. L'exemple à la fin de la section suivante indique comment inclure l'entité descendante dans la méthode de requête.

Opérations de service de domaine avec une hiérarchie de composition

Quand vous définissez une hiérarchie de composition, vous devez modifier l'interaction avec les entités parents et descendantes. La logique que vous incluez dans les services de domaine doit tenir compte du lien entre les entités. En général, vous définissez la logique de la hiérarchie à travers des méthodes de service de domaine pour l'entité parent. Dans les opérations de service de domaine pour l'entité parent, vous traitez les modifications de l'entité parent et toutes les modifications des entités descendantes.

Les règles suivantes s'appliquent aux opérations de service de domaine pour les entités avec des relations de composition :

  • Les méthodes de requête pour les entités parent ou descendantes sont autorisées ; cependant, il est recommandé de récupérer les entités descendantes dans le contexte de l'entité parent. Une exception est levée si vous modifiez une entité descendante chargée sans l'entité parent.

  • Les opérations de modification de données peuvent être ajoutées aux entités descendantes, mais les opérations autorisées sur une entité descendante sont influencées par les opérations autorisées sur l'entité parent.

    • Si la mise à jour est autorisée sur l'entité parent, la mise à jour, l'insertion et la suppression est autorisée sur l'entité descendante.

    • Si une entité parent a une méthode de mise à jour nommée, tous les descendants doivent avoir l'autorisation de mise à jour.

    • Si l'insertion ou la suppression est autorisée sur l'entité parent, alors, l'opération correspondante est autorisée de manière récursive sur les descendants.

Dans le projet client, les règles suivantes s'appliquent à l'utilisation des entités qui ont des relations de composition :

  • Quand une entité descendante contient une modification, la notification de la modification est propagée à l'entité parent. La propriété HasChanges sur l'entité parent a la valeur true.

  • Lorsqu'une entité parent est modifiée, toutes ses entités descendantes (même les entités descendantes qui n'ont pas été modifiées) sont incluses dans l'ensemble de modifications.

  • Un EntitySet public dans le contexte de domaine n'est pas généré sur le client pour les entités descendantes. Vous devez accéder à l'entité descendante via l'entité parent.

  • Une entité descendante peut être définie avec plusieurs ancêtres au même niveau, mais vous devez vous assurer qu'elle est chargée uniquement dans le contexte d'un seul ancêtre.

Les opérations de modification de données sont exécutées selon les règles suivantes :

  • Une opération de mise à jour, d'insertion ou de suppression s'exécute d'abord sur l'entité parent avant d'exécuter de manière récursive des opérations de modification de données sur les entités descendantes.

  • Si l'opération de données requise n'est pas présente dans une entité descendante, l'exécution récursive est arrêtée.

  • Lors de la mise à jour d'une entité parent, l'ordre d'exécution des opérations de données sur les descendants n'est pas spécifié.

L'exemple suivant montre les méthodes de requête, de mise à jour et de suppression de l'entité SalesOrderHeader. Les méthodes incluent la logique de traitement des modifications dans les entités descendantes.

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