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