复合层次结构

利用 WCF RIA Services,您可以为属于复合层次结构的数据类创建应用程序逻辑,这些层次结构包含由“具有”关系关联的类,其中包含对象(整体或父)控制着被包含对象(部分或子代)的创建和生命周期。例如,SalesOrderHeader实体具有 SalesOrderDetail 实体,因为有关订单的详细信息只作为订单的一部分存在。为了阐明这一点,可以对照类的子类型化来看类的组件:子类型化包括通过给比较宽泛的类型(交通工具)添加详细信息来创建比较具体的类型(小轿车)。这样就会导致在继承层次结构中详细(派生)类仍可被视为泛型(基)类,因为从这个例子来看,小轿车“(仍然)是”是交通工具。

在定义了相关类之间的复合关系之后,可以针对作为单个单元(而不是需要作为多个单独实体)的多个实体来执行数据修改操作。这会简化中间层逻辑,因为您可以为整个层次结构编写应用程序逻辑,而不是需要将该逻辑拆分,以应用于每个实体并尝试在数据操作期间协调该拆分逻辑。

理解复合层次结构

在实体的层次结构中,一个实体被称为父实体,其他相关实体被称为子代实体。父实体是表示数据的类,它是子代实体中数据的单个根。例如,SalesOrderHeader 实体是父实体,SalesOrderDetail 是子代实体。SalesOrderHeader 实体中的单个记录可以链接到 SalesOrderDetail 实体中的几个记录。

数据类是层次结构关系的一部分,通常具有以下特征:

  • 实体之间的关系可以表示为树状结构,其中子代实体连接到单个父实体。子代实体可以扩展为任意数目的级别。

  • 子代实体的生存期包含在父实体的生存期内。

  • 子代实体在父实体的上下文外没有有意义的标识。

  • 对实体的数据操作要求将这些实体作为单个单元处理。例如,在子代实体中添加、删除或更新记录要求在父实体中做相应更改。

定义复合关系

通过将 CompositionAttribute 特性应用到表示实体之间的关联的属性,定义实体之间的复合关系。下面的示例显示如何通过使用元数据类来定义 SalesOrderHeaderSalesOrderDetail 之间的复合关系。CompositionAttribute 特性位于 System.ComponentModel.DataAnnotations 命名空间中。您必须使用 usingImports 语句来引用该命名空间,以应用该特性,如以下代码中所示:

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

    }
}

CompositionAttribute 特性应用到某属性时,不自动使用父实体检索子代实体的数据。若要在查询结果中包含子代实体,必须将 IncludeAttribute 特性应用到表示子代实体的属性并在查询方法中包含该子代实体。下一节末尾的示例说明如何在查询方法中包含子代实体。

具有复合层次结构的域服务操作

定义复合层次结构时,必须更改与父实体和子代实体交互的方式。在域服务中包含的逻辑必须考虑实体之间的链接。通常,通过父实体的域服务方法定义层次结构的逻辑。在父实体的域服务操作中,您处理对父实体的修改和对子代实体的任何修改。

下列规则适用于具有复合关系的实体的域服务操作:

  • 允许有父实体和子代实体的查询方法,但是,建议您在父实体的上下文中检索子代实体。如果修改已加载的没有父实体的子代实体,将引发异常。

  • 可以将数据修改操作添加到子代实体,但是允许对子代实体的操作受允许对父实体的操作影响。

    • 如果对父实体允许更新,则允许对子代实体进行更新、插入和删除。

    • 如果父实体具有已命名的更新方法,则所有子代必须启用更新。

    • 如果对父实体允许插入或删除,则允许对子代实体递归进行相应的操作。

在客户端项目中,以下规则适用于具有复合关系的实体:

  • 子代实体包含更改时,将该更改的通知向上传播到父实体。将父实体的 HasChanges 属性设置为 true。

  • 修改父实体时,它的所有子代实体(甚至未更改的那些子代)都将包括在更改集中。

  • 不在子代实体的客户端生成域上下文中的公共 EntitySet。必须通过父实体访问子代实体。

  • 可以使用同一级别的多个上级定义子代实体,但是必须确保只在一个上级的上下文中加载它。

根据以下规则执行数据更改操作:

  • 首先执行对父实体的更新、插入或删除操作,然后递归执行对子代实体的任何数据修改操作。

  • 如果在子代实体中没有提供所需的数据操作,将停止执行递归。

  • 更新父实体时,不指定对子代的数据操作的执行顺序。

下面的示例显示用于查询、更新和删除 SalesOrderHeader 实体的方法。这些方法包括处理子代实体中的更改的逻辑。

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