Festgelegte Hierarchien

Mit WCF RIA Services können Sie Anwendungslogik für Datenklassen erstellen, die zu festgelegten Hierarchien mit Klassen gehören, die durch "enthält ein"-Beziehungen miteinander verknüpft sind, bei denen das enthaltende Objekt (insgesamt oder übergeordnet) die Erstellung und Lebensdauer des enthaltenen Objekts (Teil oder untergeordnet) steuert. Zum Beispiel enthält die SalesOrderHeader-Entität eine SalesOrderDetail-Entität, da die Details zu einem Auftrag nur als Teil des Auftrags vorkommen können. Zur Verdeutlichung: Die Festlegung von Klassen kann im Vergleich zur Definition von Untertypen von Klassen gesehen werden, bei der ein spezifischerer Typ (ein PKW) durch das Hinzufügen von Details zu einem allgemeineren Typ (ein Fahrzeug) erstellt wird. Hierdurch entsteht eine Vererbungshierarchie, bei der die spezifischere (abgeleitete) Klasse weiterhin wie der allgemeine (Basis-) Typ behandelt werden kann, da, um beim Beispiel zu bleiben, ein PKW "nach wie vor" ein Fahrzeug ist.

Nachdem Sie die festgelegte Beziehung zwischen den relevanten Klassen definiert haben, können Sie Datenänderungsvorgänge für die Entitäten ausführen, die die Entitäten als eine Einheit behandeln, anstatt Vorgänge für separate Entitäten behandeln zu müssen. Dies vereinfacht die Logik der mittleren Ebene, da Anwendungslogik für die gesamte Hierarchie geschrieben werden kann, anstatt diese Logik für die einzelnen Entitäten aufzuteilen und dann bei Datenvorgängen zu koordinieren.

Grundlegendes zu festgelegten Hierarchien

In einer Hierarchie von Entitäten wird eine Entität als übergeordnete Entität bezeichnet und die anderen verknüpften Entitäten als Nachfolgerentitäten. Die übergeordnete Entität ist die Klasse, die Daten darstellt, d. h. der einzige Stamm für die Daten in den Nachfolgerentitäten. Die SalesOrderHeader-Entität ist z. B. die übergeordnete Entität und SalesOrderDetail eine Nachfolgerentität. Ein einzelner Datensatz in der SalesOrderHeader-Entität kann mit mehreren Datensätzen in der SalesOrderDetail-Entität verknüpft werden.

Datenklassen, die Teil einer hierarchischen Beziehung sind, besitzen normalerweise die folgenden Eigenschaften:

  • Die Beziehung zwischen den Entitäten kann als Struktur dargestellt werden, in der die Nachfolgerentitäten mit nur einer übergeordneten Entität verbunden sind. Die Nachfolgerentitäten können sich über eine beliebige Anzahl von Ebenen erstrecken.

  • Die Lebensdauer einer Nachfolgerentität ist in der Lebensdauer der übergeordneten Entität enthalten.

  • Die Nachfolgerentität verfügt außerhalb des Kontexts der übergeordneten Entität nicht über eine sinnvolle Identität.

  • Für Datenvorgänge an den Entitäten müssen die Entitäten als eine Einheit behandelt werden. Das Hinzufügen, Löschen oder Aktualisieren eines Datensatzes in der Nachfolgerentität erfordert z. B. eine entsprechende Änderung in der übergeordneten Entität.

Definieren einer festgelegten Beziehung

Eine festgelegte Beziehung zwischen Entitäten wird definiert, indem das CompositionAttribute-Attribut auf die Eigenschaft angewendet wird, die die Zuordnung zwischen Entitäten darstellt. Im folgenden Beispiel wird gezeigt, wie eine festgelegte Beziehung zwischen SalesOrderHeader und SalesOrderDetail mithilfe einer Metadatenklasse definiert wird. Das CompositionAttribute-Attribut ist im System.ComponentModel.DataAnnotations-Namespace enthalten. Sie müssen mithilfe der using- oder Imports-Anweisung auf diesen Namespace verweisen, um das Attribut wie im folgenden Code dargestellt anzuwenden.

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

    }
}

Wenn Sie das CompositionAttribute-Attribut auf eine Eigenschaft anwenden, werden die Daten aus der Nachfolgerentität nicht automatisch mit der übergeordneten Entität abgerufen. Um die Nachfolgerentität in Abfrageergebnisse einzuschließen, müssen Sie das IncludeAttribute-Attribut auf die Eigenschaft anwenden, die die Nachfolgerentität darstellt, und die Nachfolgerentität in die Abfragemethode einschließen. Im Beispiel am Ende des nächsten Abschnitts wird gezeigt, wie die Nachfolgerentität in die Abfragemethode eingeschlossen wird.

Domänendienstvorgänge mit festgelegter Hierarchie

Wenn Sie eine festgelegte Hierarchie definieren, müssen Sie die Art und Weise ändern, wie Sie mit den übergeordneten Entitäten und den Nachfolgerentitäten interagieren. In der in Domänendiensten verwendeten Logik muss der Link zwischen den Entitäten berücksichtigt werden. In der Regel definieren Sie die Logik für die Hierarchie anhand von Domänendienstmethoden für die übergeordnete Entität. In den Domänendienstvorgängen für die übergeordnete Entität verarbeiten Sie Änderungen an der übergeordneten Entität und Änderungen an den Nachfolgerentitäten.

Die folgenden Regeln gelten für Domänendienstvorgänge für Entitäten mit festgelegten Beziehungen:

  • Abfragemethoden für übergeordnete Entitäten oder Nachfolgerentitäten sind zulässig. Es wird jedoch empfohlen, Nachfolgerentitäten im Kontext der übergeordneten Entität abzurufen. Wenn Sie eine Nachfolgerentität ändern, die ohne die übergeordnete Entität geladen wurde, wird ein Ausnahmefehler ausgelöst.

  • Nachfolgerentitäten können Datenänderungsvorgänge hinzugefügt werden, die zulässigen Vorgänge für eine Nachfolgerentität werden jedoch durch die zulässigen Vorgänge für die übergeordnete Entität beeinflusst.

    • Wenn das Aktualisieren für die übergeordnete Entität zulässig ist, sind Aktualisieren, Einfügen und Löschen für die Nachfolgerentität erlaubt.

    • Wenn eine übergeordnete Entität über eine benannte Updatemethode verfügt, muss das Aktualisieren für alle Nachfolger aktiviert sein.

    • Wenn das Einfügen oder Löschen für die übergeordnete Entität zulässig ist, ist der entsprechende Vorgang rekursiv für die Nachfolger erlaubt.

Innerhalb des Clientprojekts gelten die folgenden Regeln für die Verwendung von Entitäten mit festgelegten Beziehungen:

  • Wenn eine Nachfolgerentität eine Änderung enthält, wird eine Benachrichtigung über die Änderung nach oben an die übergeordnete Entität weitergegeben. Die HasChanges-Eigenschaft der übergeordneten Entität wird auf "true" festgelegt.

  • Wenn eine übergeordnete Entität geändert wird, werden alle zugehörigen Nachfolgerentitäten (auch Nachfolger, die nicht geändert wurden) in das Changeset eingeschlossen.

  • Auf dem Client wird kein öffentliches EntitySet im Domänenkontext für Nachfolgerentitäten generiert. Sie müssen über die übergeordnete Entität auf die Nachfolgerentität zugreifen.

  • Eine Nachfolgerentität kann mit mehreren Vorgängern auf der gleichen Ebene definiert werden, Sie müssen jedoch sicherstellen, dass sie im Kontext eines einzigen Vorgängers geladen wird.

Datenänderungsvorgänge werden gemäß den folgenden Regeln ausgeführt:

  • Ein Aktualisierungs-, Einfüge- oder Löschvorgang wird zuerst für die übergeordnete Entität ausgeführt, bevor Datenänderungsvorgänge rekursiv für die Nachfolgerentitäten ausgeführt werden.

  • Wenn der erforderliche Datenvorgang in einer Nachfolgerentität nicht vorhanden ist, wird die rekursive Ausführung beendet.

  • Beim Aktualisieren einer übergeordneten Entität wird die Ausführungsreihenfolge für Datenvorgänge an Nachfolgern nicht angegeben.

Im folgenden Beispiel werden die Methoden zum Abfragen, Aktualisieren und Löschen der SalesOrderHeader-Entität veranschaulicht. Die Methoden enthalten Logik zum Verarbeiten von Änderungen in den Nachfolgerentitäten.

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