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