Export (0) Print
Expand All

Compositional Hierarchies

WCF RIA Services

[WCF RIA Services Version 1 Service Pack 2 is compatible with either .NET framework 4 or .NET Framework 4.5, and with either Silverlight 4 or Silverlight 5.]

WCF RIA Services enables you to create application logic for data classes that belong to compositional hierarchies that contain classes associated by “has a” relationships in which the containing object (the whole or the parent) controls the creation and lifetime of the contained object (the part or the descendent). For example, the SalesOrderHeader entity has a SalesOrderDetail entity as the details concerning an order only exist as part of the order. For clarify, the composition of classes may be contrasted with the subtyping of classes, which consists of creating a more specific type (a car) by adding details to a more general type (a vehicle). This results in an inheritance hierarchy in which the detailed (derived) class can still be treated as the general (base) type because, to use the example, a car “is (still) a” vehicle.

After defining the compositional relationship between the relevant classes, you can perform data modification operations on the entities that treat them as a single unit instead of having to deal with them as separate entities. This simplifies middle-tier logic because you can write application logic for the entire hierarchy instead of having to split that logic to apply to each entity and then attempt to coordinate that split logic during data operations.

In a hierarchy of entities, one entity is referred to as the parent entity and the other related entities are referred to as descendant entities. The parent entity is the class that represents data, which is the single root for the data in the descendant entities. For example, the SalesOrderHeader entity is the parent entity, and SalesOrderDetail is a descendant entity. A single record in the SalesOrderHeader entity can be linked to several records in the SalesOrderDetail entity.

Data classes that are part of a hierarchical relationship typically have the following characteristics:

  • The relationship between the entities can be represented as tree with the descendant entities connected to a single parent entity. The descendant entities can extend for any number of levels.

  • The lifetime of a descendant entity is contained within the lifetime of the parent entity.

  • The descendant entity does not have a meaningful identity outside of the context of the parent entity.

  • Data operations on the entities require that the entities be treated as single unit. For example, adding, deleting, or updating a record in the descendant entity requires a corresponding change in the parent entity.

You define a compositional relationship between entities by applying the CompositionAttribute attribute to the property that represents the association between entities. The following example shows how to define a compositional relationship between SalesOrderHeader and SalesOrderDetail by using a metadata class. The CompositionAttribute attribute is in the System.ComponentModel.DataAnnotations namespace. You have to reference that namespace by using the using or Imports statement to apply the attribute as shown in the following code.

[MetadataTypeAttribute(typeof(SalesOrderHeader.SalesOrderHeaderMetadata))]
public partial class SalesOrderHeader
{
    internal sealed class SalesOrderHeaderMetadata
    {
        private SalesOrderHeaderMetadata()
        {
        }

        [Include]
        [Composition]
        public EntitySet<SalesOrderDetail> SalesOrderDetails;

    }
}


When you apply the CompositionAttribute attribute to a property, the data from the descendant entity is not automatically retrieved with the parent entity. To include the descendent entity in query results, you must apply the IncludeAttribute attribute to the property that represents the descendant entity and include the descendant entity in the query method. The example at the end of the next section shows how to include the descendant entity in the query method.

When you define a compositional hierarchy, you must change the way you interact with the parent and descendant entities. The logic you include in domain services must account for the link between the entities. Typically, you define the logic for the hierarchy through domain service methods for the parent entity. In the domain service operations for the parent entity, you process modifications to the parent entity and any modifications to the descendant entities.

The following rules apply to domain service operations for entities with compositional relationships:

  • Query methods for parent or descendant entities are permitted; however, it is recommended that you retrieve descendant entities in the context of the parent entity. An exception is thrown if you modify a descendant entity that has been loaded without the parent entity.

  • Data modification operations can be added to descendant entities, but the permitted operations on a descendant entity are influenced by the permitted operations on the parent entity.

    • If update is permitted on the parent entity, then update, insert, and delete are permitted on the descendant entity.

    • If a parent entity has a named update method, then all descendants must have update enabled.

    • If insert or delete is permitted on the parent entity, then the corresponding operation is permitted recursively on the descendants.

Within the client project, the following rules apply to using entities that have compositional relationships:

  • When a descendant entity contains a change, notification of the change is propagated up to the parent entity. The HasChanges property on the parent entity is set to true.

  • When a parent entity is modified, all of its descendant entities (even those descendants that have not changed) are included in the change set.

  • A public EntitySet in the domain context is not generated on the client for descendant entities. You must access the descendant entity through the parent entity.

  • A descendant entity can be defined with more than one ancestor at the same level, but you must ensure that it is loaded within the context of only a single ancestor.

Data modification operations are executed according to the following rules:

  • An update, insert, or delete operation is executed first on the parent entity before recursively executing any data modification operations on the descendent entities.

  • If the required data operation is not present in a descendant entity, the recursive execution is stopped.

  • When updating a parent entity, the order of execution for data operations on descendants is not specified.

The following example shows the methods for querying, updating, and deleting the SalesOrderHeader entity. The methods include logic to process changes in the descendant entities.

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

    }
}


Show:
© 2014 Microsoft