Compartir a través de


Jerarquías composicionales

WCF RIA Services permite crear lógica de aplicación para las clases de datos que pertenecen a jerarquías composicionales que contienen clases asociadas mediante relaciones de pertenencia en que el objeto contenedor (completo o primario) controla la creación y la duración del objeto contenido (parcial o descendiente). Por ejemplo, la entidad SalesOrderHeader tiene una entidad SalesOrderDetail, ya que los detalles relacionados con un pedido solamente existen como parte del pedido. A efectos de clarificación, la composición de clases se puede contrastar con los subtipos de clases, que consiste en la creación de un tipo (un automóvil) más específico mediante la adición de detalles a un tipo más general (un vehículo). Esto se traduce en una jerarquía de herencia en que la clase detallada (derivada) puede seguir tratándose como el tipo general (base) porque, para utilizar el ejemplo, un automóvil "sigue siendo un" vehículo.

Después de definir la relación de composición entre las clases pertinentes, puede realizar operaciones de modificación de datos en las entidades que las tratan como una unidad única en lugar de tener que tratarlas como entidades individuales. Esto simplifica la lógica de nivel intermedio, ya que puede escribir lógica de aplicación para la totalidad de la jerarquía en lugar de tener que dividir dicha lógica para aplicarla a cada entidad e intentar coordinar esa lógica dividida durante las operaciones de datos.

Descripción de las jerarquías composicionales

En una jerarquía de entidades, una entidad se conoce como la entidad primaria y las otras entidades relacionadas se conocen como entidades descendientes. La entidad primaria es la clase que representa datos, que es la raíz única para los datos en las entidades descendientes. Por ejemplo, la entidad SalesOrderHeader es la entidad primaria y SalesOrderDetail es una entidad descendiente. Un registro único en la entidad SalesOrderHeader se puede vincular a varios registros en la entidad SalesOrderDetail.

Las clases de datos que forman parte de una relación jerárquica tienen normalmente las siguientes características:

  • La relación entre las entidades se puede representar como un árbol con las entidades descendientes conectadas a una entidad primaria única. Las entidades descendientes se pueden extender para cualquier número de niveles.

  • La duración de una entidad descendiente está contenida en la duración de la entidad primaria.

  • La entidad descendiente no tiene una identidad significativa fuera del contexto de la entidad primaria.

  • Las operaciones de datos en las entidades requieren que estas se traten como una unidad única. Por ejemplo, la operación de adición, eliminación o actualización de un registro en la entidad descendiente requiere un cambio correspondiente en la entidad primaria.

Definir una relación de composición

Una relación de composición entre entidades se define aplicando el atributo CompositionAttribute a la propiedad que representa la asociación entre entidades. En el siguiente ejemplo se muestra cómo definir una relación de composición entre SalesOrderHeader y SalesOrderDetail mediante el uso de una clase de metadatos. El atributo CompositionAttribute está en el espacio de nombres System.ComponentModel.DataAnnotations. Tiene que hacer referencia a ese espacio de nombres utilizando la instrucción Imports o using para aplicar el atributo como se muestra en el código siguiente.

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

    }
}

Cuando aplique el atributo CompositionAttribute a una propiedad, los datos de la entidad descendiente no se recuperarán automáticamente con la entidad primaria. Para incluir la entidad descendiente en los resultados de la consulta, debe aplicar el atributo IncludeAttribute a la propiedad que representa la entidad descendiente e incluir la entidad descendiente en el método de consulta. En el ejemplo que figura al final de la sección siguiente se muestra cómo incluir la entidad descendiente en el método de consulta.

Operaciones de servicio de dominio con jerarquía composicional

Cuando defina una jerarquía composicional, deberá cambiar la forma de interactuar con las entidades primaria y descendientes. La lógica que incluya en los servicios de dominio deberá justificar el vínculo entre las entidades. Normalmente, la lógica para la jerarquía se define a través de los métodos de servicio de dominio para la entidad primaria. En las operaciones de servicio de dominio para la entidad primaria, se procesan las modificaciones realizadas en la entidad primaria y las modificaciones efectuadas en las entidades descendientes.

Las siguientes reglas se aplican a las operaciones de servicio de dominio para las entidades con relaciones de composición:

  • Se permiten métodos de consulta para la entidad primaria o las entidades descendientes; sin embargo, se recomienda recuperar las entidades descendientes en el contexto de la entidad primaria. Se produce una excepción si se modifica una entidad descendiente que se haya cargado sin la entidad primaria.

  • Las operaciones de modificación de datos se pueden agregar a las entidades descendientes, pero las operaciones permitidas en la entidad primaria influyen en las operaciones permitidas en una entidad descendiente.

    • Si se permite la actualización en la entidad primaria, se permiten las operaciones de actualización, inserción y eliminación en la entidad descendiente.

    • Si una entidad primaria tiene un método de actualización con nombres, todas las entidades descendientes deben tener habilitada la actualización.

    • Si se permite la operación de inserción o eliminación en la entidad primaria, la operación correspondiente se permite de forma recursiva en las entidades descendientes.

Dentro del proyecto de cliente, las reglas siguientes se aplican al uso de entidades que tienen relaciones de composición:

  • Cuando una entidad descendiente contiene un cambio, la notificación del cambio se propaga hasta la entidad primaria. El valor de la propiedad HasChanges en la entidad primaria se establece en true.

  • Cuando se modifica una entidad primaria, todas sus entidades descendientes (incluso las entidades descendientes que no han cambiado) se incluyen en el conjunto de cambios.

  • Un elemento EntitySet público en el contexto de dominio no se genera en el cliente para las entidades descendientes. Se debe tener acceso a la entidad descendiente a través de la entidad primaria.

  • Una entidad descendiente se puede definir con más de un antecesor en el mismo nivel, pero hay que asegurarse de que se cargue únicamente dentro del contexto de un solo antecesor.

Las operaciones de modificación de datos se ejecutan de acuerdo con las reglas siguientes:

  • Una operación de actualización, inserción o eliminación se ejecuta primero en la entidad primaria antes de que se ejecuten de forma recursiva las operaciones de modificación de datos en las entidades descendientes.

  • Si la operación de datos necesaria no se encuentra en una entidad descendiente, se detiene la ejecución recursiva.

  • Cuando se actualiza una entidad primaria, no se especifica el orden de ejecución para las operaciones de datos en entidades descendientes.

En el siguiente ejemplo se muestran los métodos para consultar, actualizar y eliminar la entidad SalesOrderHeader. Los métodos incluyen la lógica para procesar los cambios en las entidades descendientes.

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