Vererbung in Datenmodellen

WCF RIA Services ermöglicht Ihnen die Verwendung von Entitäten, die Teil einer Vererbungshierarchie sind. Ein Vererbungsmodell enthält eine Datenklasse, die von einer anderen Datenklasse abgeleitet ist. Ein polymorphes Vererbungsmodell kann z. B. eine Customer-Entität und zwei weitere Entitäten (PublicSectorCustomer und PrivateSectorCustomer) enthalten, die von Customer abgleitet sind. In RIA Services können Sie im Domänendienst Abfragemethoden schreiben, die eine Auflistung von Stammtypen und anderen vom Stammtyp abgeleiteten Typen zurückgeben. Sie können auch eine Abfragemethode schreiben, die eine Auflistung zurückgibt, die nur die abgeleiteten Typen enthält. Datenänderungsmethoden für einen Stammtyp oder einen abgeleiteten Typ werden ebenfalls unterstützt.

Datenmodell

Die Datenklassen für das Vererbungsmodell werden im Serverprojekt wie alle Datenklassen definiert. Als Objektmodell können entweder automatisch generierte Klassen aus der Datenzugriffsebene oder manuell erstellte Datenklassen verwendet werden.

Es ist nicht erforderlich, die gesamte Hierarchie durch den Domänendienst verfügbar zu machen. Stattdessen gilt die am wenigsten abgeleitete Klasse in der Hierarchie, die von einem Domänendienst verfügbar gemacht wird, als Stammtyp für Interaktionen vom Client. Vom Stammtyp abgeleitete Typen können ebenfalls für den Client verfügbar gemacht werden. In der Stammklasse müssen Sie das KnownTypeAttribute-Attribut einschließen oder einen der abgeleiteten Typen, die Sie verfügbar machen möchten. Sie können abgeleitete Typen auslassen, indem Sie sie nicht in das KnownTypeAttribute-Attribut einschließen. In diesem Fall müssen Sie jedoch sicherstellen, dass keine Instanzen dieser ausgelassenen Typen von einer Abfrage zurückgegeben werden. Das folgende Beispiel zeigt ein manuell erstelltes Datenmodell, das die Customer-Basisklasse und zwei abgeleitete Klassen, PrivateSectorCustomer und PublicSectorCustomer, enthält. Customer ist der Stammtyp für Datenvorgänge. Die beiden abgeleiteten Klassen werden daher wie mit den GetType-Parametern des Attributs angegeben in das KnownTypeAttribute-Attribut für die Customer-Klasse eingeschlossen.

<KnownType(GetType(PublicSectorCustomer)), KnownType(GetType(PrivateSectorCustomer))> _
Public Class Customer
    <Key()> _
    Public Property CustomerID As Integer
    Public Property FirstName As String
    Public Property LastName As String
    Public Property Address As String
    Public Property City As String
    Public Property StateProvince As String
    Public Property PostalCode As String
    <Association("CustomerOrders", "CustomerID", "CustomerID")> _
    Public Property Orders As List(Of Order)
End Class

Public Class PublicSectorCustomer
    Inherits Customer
    Public Property GSARegion As String
End Class

Public Class PrivateSectorCustomer
    Inherits Customer
    Public Property CompanyName As String
End Class
[KnownType(typeof(PublicSectorCustomer)), KnownType(typeof(PrivateSectorCustomer))]
public class Customer
{
    [Key]
    public int CustomerID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string StateProvince { get; set; }
    public string PostalCode { get; set; }
    [Association("CustomerOrders", "CustomerID", "CustomerID")]
    public List<Order> Orders { get; set; }
}

public class PublicSectorCustomer : Customer
{
    public string GSARegion { get; set; }
}

public class PrivateSectorCustomer : Customer
{
    public string CompanyName { get; set; }
}

Polymorphe Abfragen

Nachdem Sie das Datenmodell definiert haben, erstellen Sie einen Domänendienst, der die Typen für den Client verfügbar macht. Wenn Sie einen Typ in einer Abfragemethode verfügbar machen, können Sie diesen Typ und alle abgeleiteten Typen zurückgeben. Eine Abfrage, die eine Auflistung von Customer-Entitäten zurückgibt, kann z. B. PrivateSectorCustomer-Objekte und PublicSectorCustomer-Objekte enthalten. Sie können auch angeben, dass von einer Abfragemethode nur ein abgeleiteter Typ zurückgegeben wird. Das folgende Beispiel zeigt Abfragemethoden, die jeden dieser drei Typen zurückgeben.

Public Function GetCustomers() As IQueryable(Of Customer)
    Return context.Customers
End Function

Public Function GetCustomersByState(ByVal state As String) As IQueryable(Of Customer)
    Return context.Customers.Where(Function(c) c.StateProvince = state)
End Function

Public Function GetCustomersByGSARegion(ByVal region As String) As IQueryable(Of PublicSectorCustomer)
    Return context.Customers.OfType(Of PublicSectorCustomer)().Where(Function(c) c.GSARegion = region)
End Function

Public Function GetPrivateSectorByPostalCode(ByVal postalcode As String) As IQueryable(Of PrivateSectorCustomer)
    Return context.Customers.OfType(Of PrivateSectorCustomer)().Where(Function(c) c.PostalCode = postalcode)
End Function
public IQueryable<Customer> GetCustomers()
{
    return context.Customers;
}

public IQueryable<Customer> GetCustomersByState(string state)
{
    return context.Customers.Where(c => c.StateProvince == state);
}

public IQueryable<PublicSectorCustomer> GetCustomersByGSARegion(string region)
{
    return context.Customers.OfType<PublicSectorCustomer>().Where(c => c.GSARegion == region);
}

public IQueryable<PrivateSectorCustomer> GetPrivateSectorByPostalCode(string postalcode)
{
    return context.Customers.OfType<PrivateSectorCustomer>().Where(c => c.PostalCode == postalcode);
}

Generierter Code für das Clientprojekt

Wenn Sie die Projektmappe erstellen, wird im Clientprojekt Code für die Vererbungshierarchie generiert, die Sie im Domänendienst verfügbar gemacht haben. Die Stammklasse der Hierarchie wird generiert und von der Entity-Klasse abgeleitet. Jede abgeleitete Klasse wird generiert und von der jeweiligen Basisklasse abgeleitet. In der DomainContext-Klasse wird nur eine EntitySet-Eigenschaft generiert, die Objekte des Stammtyps akzeptiert. Für jede Abfrage wird ein EntityQuery-Objekt generiert, das den im Domänendienstvorgang angegebenen Typ zurückgibt.

Das folgende Beispiel zeigt eine vereinfachte Version des Codes, der im Clientprojekt für die Abfragemethoden und Datenklassen in den vorherigen Beispielen generiert wird. Das Beispiel zeigt nicht den gesamten Code, der in den generierten Klassen vorhanden ist, sondern dient lediglich zur Veranschaulichung einiger wichtiger Eigenschaften und Methoden.

<DataContract([Namespace]:="http://schemas.datacontract.org/2004/07/SilverlightApplication14.Web"),  _
 KnownType(GetType(PrivateSectorCustomer)),  _
 KnownType(GetType(PublicSectorCustomer))>  _
Partial Public Class Customer
    Inherits Entity
    
    Public Property Address() As String
    Public Property City() As String
    Public Property CustomerID() As Integer
    Public Property FirstName() As String
    Public Property LastName() As String
    Public Property PostalCode() As String
    Public Property StateProvince() As String
        
    Public Overrides Function GetIdentity() As Object
    End Function
End Class

<DataContract([Namespace]:="http://schemas.datacontract.org/2004/07/SilverlightApplication14.Web")> _
Partial Public NotInheritable Class PrivateSectorCustomer
    Inherits Customer

    Public Property CompanyName() As String
End Class

<DataContract([Namespace]:="http://schemas.datacontract.org/2004/07/SilverlightApplication14.Web")> _
Partial Public NotInheritable Class PublicSectorCustomer
    Inherits Customer

    Public Property GSARegion() As String
End Class

Partial Public NotInheritable Class CustomerDomainContext
    Inherits DomainContext
    
    Public Sub New()
    End Sub
    
    Public Sub New(ByVal serviceUri As Uri)
    End Sub
    
    Public Sub New(ByVal domainClient As DomainClient)
    End Sub
    
    Public ReadOnly Property Customers() As EntitySet(Of Customer)
        Get
        End Get
    End Property
    
    Public Function GetCustomersQuery() As EntityQuery(Of Customer)
    End Function
    
    Public Function GetCustomersByGSARegionQuery(ByVal region As String) As EntityQuery(Of PublicSectorCustomer)
    End Function
    
    Public Function GetCustomersByStateQuery(ByVal state As String) As EntityQuery(Of Customer)
    End Function
    
    Public Function GetPrivateSectorByPostalCodeQuery(ByVal postalcode As String) As EntityQuery(Of PrivateSectorCustomer)
    End Function
End Class
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
[KnownType(typeof(PrivateSectorCustomer))]
[KnownType(typeof(PublicSectorCustomer))]
public partial class Customer : Entity
{   
    public string Address { get; set; }
    public string City { get; set; }
    [Key()]
    public int CustomerID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PostalCode { get; set; }
    public string StateProvince { get; set; }
 
    public override object GetIdentity();
    
}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PrivateSectorCustomer : Customer
{
    public string CompanyName { get; set; }
}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PublicSectorCustomer : Customer
{
    public string GSARegion { get; set; }
}

public sealed partial class CustomerDomainContext : DomainContext
{
    public CustomerDomainContext(); 
    public CustomerDomainContext(Uri serviceUri);
    public CustomerDomainContext(DomainClient domainClient);
    
    public EntitySet<Customer> Customers { get; }

    public EntityQuery<Customer> GetCustomersQuery(); 
    public EntityQuery<PublicSectorCustomer> GetCustomersByGSARegionQuery(string region);
    public EntityQuery<Customer> GetCustomersByStateQuery(string state);
    public EntityQuery<PrivateSectorCustomer> GetPrivateSectorByPostalCodeQuery(string postalcode);
}

Datenänderungen

Sie können auch Domänendienstmethoden zum Aktualisieren, Einfügen und Löschen von Objekten in der Vererbungshierarchie hinzufügen. Wie bei Abfragemethoden können Sie entweder einen Stammtyp oder einen abgeleiteten Typ für die Vorgänge angeben. Alle für einen abgeleiteten Typ aktivierten Aktualisierungs-, Einfüge- oder Löschvorgänge müssen jedoch auch für den Stammtyp aktiviert sein. Sie können auch benannte Updatemethoden für beliebige Typen in der Hierarchie hinzufügen. Die entsprechende benannte Updatemethode für den Client wird für den in der Methode angegebenen Typ generiert.

Jedes Mal, wenn der Client Änderungen zur Verarbeitung an den Server sendet, wird die am weitesten abgeleitete Version der jeweiligen Einfüge-, Update- oder Löschmethode Objekt für Objekt ausgeführt. Sie müssen festlegen, welche Aktionen in den Methoden für abgeleitete Typen ausgeführt werden sollen, z. B., ob die entsprechenden Methoden für den Stammtyp aufgerufen werden.

Das folgende Beispiel zeigt die Signatur für zwei Updatemethoden und eine benannte Updatemethode. Der Code zum Implementieren der Logik, mit der die Werte aktualisiert werden, ist nicht enthalten.

Public Sub UpdateCustomer(ByVal customer As Customer)
    ' implement 
End Sub

Public Sub UpdatePublicSectorCustomer(ByVal customer As PublicSectorCustomer)
    ' implement 
End Sub

Public Sub EnrollInRewardsProgram(ByVal customer As PrivateSectorCustomer)
    ' implement
End Sub
public void UpdateCustomer(Customer customer) { /* implement */ }
public void UpdatePublicSectorCustomer(PublicSectorCustomer customer) { /* implement */ }
public void EnrollInRewardsProgram(PrivateSectorCustomer customer) { /* implement */ }

Zuordnungen

Eine Zuordnung kann in der Stammklasse oder in einer der von der Basisklasse abgeleiteten Klassen definiert werden. Um eine Zuordnung zwischen zwei Datenklassen zu definieren, wenden Sie das AssociationAttribute-Attribut an. Im Datenmodellbeispiel ist eine Zuordnung zwischen Customer und Order definiert. Wenn eine Zuordnung auf einen Stammtyp angewendet wird, enthalten alle abgeleiteten Typen ebenfalls diese Zuordnung.

Sie können zusätzliche Zuordnungen auf abgeleitete Typen anwenden, die für den Stammtyp nicht verfügbar sind.

Allgemeine Regeln für die Verwendung der Vererbung

Die folgenden Regeln gelten bei der Verwendung der Vererbung in RIA Services.

  • Die Vererbung wird nur für Entitätstypen unterstützt. Nicht-Entitätstypen werden als der Typ behandelt, der in der Signatur des Domänendienstvorgangs angegeben ist.

  • Schnittstellentypen werden nicht für Rückgabewerte oder Parameter in Domänendienstvorgängen unterstützt.

  • Der Satz von Typen in einer Vererbungshierarchie muss zum Zeitpunkt der Codegenerierung bekannt sein. Das zum Zeitpunkt der Codegenerierung nicht angegebene Verhalten für das Zurückgeben eines Werts ist "nicht definiert" und implementierungsabhängig.

  • Der virtual-Modifizierer für öffentliche Eigenschaften und Felder für einen Entitätstyp ist zulässig, wird beim Generieren des entsprechenden Entitätstyps für den Client jedoch ignoriert.

  • Methodenüberladungen für Domänendienstvorgänge sind nicht zulässig.

  • Das new-Schlüsselwort (C#) und das Shadows-Schlüsselwort (Visual Basic) für öffentliche Eigenschaften sind nicht für Entitätstypen zulässig und führen beim Generieren des Clientcodes zu einem Fehler.

  • Mit der Vererbung zusammenhängende LINQ-Abfragefunktionen können nicht zur Ausführung von Domänendienstmethoden übersetzt werden. Insbesondere werden die Operatoren und Methoden OfType<T>, is, as und GetType() nicht unterstützt. Diese Operatoren können in LINQ to Objects-Abfragen jedoch direkt für EntitySet oder EntityCollection verwendet werden.

Entitätsvererbungshierarchie

Die folgenden Regeln gelten für die Definition der Vererbungshierarchie.

  • Alle bekannten abgeleiteten Entitätstypen, die durch einen Domänendienst verfügbar gemacht werden, werden mit dem System.Runtime.Serialization.KnownTypeAttribute-Attribut angegeben.

  • Bekannte Typen in der Hierarchie müssen für den Stammtyp in der Hierarchie angegeben werden, die durch einen Domänendienst verfügbar gemacht wird.

  • Jede Klasse im bekannten Typensatz muss public sein.

  • Eine oder mehrere Klassen in der Hierarchie können abstract sein.

  • Beim Deklarieren von bekannten Typen können Sie eine oder mehrere Klassen in der Hierarchie auslassen. Die von einer ausgelassenen Klasse abgeleiteten Klassen werden vereinfacht und basierend auf der nächsthöheren übergeordneten Klasse in der Deklaration bekannter Typen erneut einem übergeordneten Element in der Vererbungshierarchie zugewiesen. Eigenschaften der ausgelassenen Klassen werden automatisch für alle verfügbar gemachten Typen generiert, die von diesen Klassen abgeleitet sind.

  • Die Stammklasse muss mindestens über eine mit dem KeyAttribute-Attribut markierte Eigenschaft verfügen. Sie können das Attribut auf eine Eigenschaft anwenden, die in einem nicht verfügbar gemachten Basistyp dieses Stammtyps definiert ist. Eine öffentliche Entitätseigenschaft einer ausgelassenen Entitätsklasse in der Hierarchie wird automatisch für einen verfügbar gemachten Entitätstyp generiert, der von dieser Klasse abgeleitet ist.

  • Die Deklaration und Verwendung von Zuordnungen bleibt unverändert.

DomainService-Vorgänge

Die folgenden Regeln gelten für die Definition von Domänendienstvorgängen für Entitäten in einer Vererbungshierarchie.

  • Mindestens eine Abfragemethode muss dem Stammtyp in der Hierarchie entsprechen. Für zusätzliche Abfragevorgänge kann ein weiter abgeleiteter Typ für den Rückgabewert verwendet werden.

  • Abfragevorgänge können für Methoden, die polymorphe Ergebnisse zurückgeben, einen Stammtyp zurückgeben.

  • Wenn ein Aktualisierungs-, Einfüge- oder Löschvorgang für einen Typ in der Hierarchie definiert wird, muss der gleiche Vorgang für den Stammtyp in der Hierarchie definiert werden. Es ist nicht möglich, einen Vorgang selektiv nur für bestimmte Typen in der Hierarchie zu definieren.

  • Für benutzerdefinierte Vorgänge kann ein Stammtyp oder ein abgeleiteter Typ für das Entitätsargument verwendet werden. Wenn der tatsächliche Typ einer Instanz vom Typ im benutzerdefinierten Vorgang abgeleitet ist, ist der Vorgang zulässig.

  • Die DomainServiceDescription-Klasse gibt die zutreffendste Update-, Einfüge- oder Löschmethode für einen Typ und alle anwendbaren Abfragemethoden zurück.

TypeDescriptionProvider

Die folgenden Regeln gelten für den TypeDescriptionProvider (TDP):

  • Wenn die Stammklasse in der Hierarchie durch eine Abfragemethode oder ein IncludeAttribute-Attribut verfügbar gemacht wird, leitet der TypeDescriptionProvider für LINQ to SQL und das Entity Framework automatisch KnownTypeAttribute-Attributdeklarationen für Entitäten ab. Der bekannte Typ wird nicht abgeleitet, wenn nur ein abgeleiteter Typ durch eine Abfragemethode oder ein IncludeAttribute-Attribut verfügbar gemacht wird.

  • Im Dialogfeld Neue Domänendienstklasse hinzufügen können keine abgeleiteten Entitätstypen ausgewählt werden. Die Abfrage-, Einfüge-, Update- oder Löschmethoden für die abgeleiteten Typen müssen manuell erstellt werden.

Generierter Code

Die folgenden Regeln gelten für den Code, der im Clientprojekt für Entitäten in einer Vererbungshierarchie generiert wird.

  • Es wird genau eine EntitySet-Klasse für jede Vererbungshierarchie generiert. Der Typparameter des EntitySet ist der Stammtyp in der bekannten Vererbungshierarchie.

  • Für jeden bekannten Typ in der Vererbungshierarchie wird ein entsprechender Entitätstyp generiert.

  • Benutzerdefinierte Methoden werden für den Typ generiert, für den sie angegeben werden, und sind für alle abgeleiteten Typen verfügbar.

  • Die Konstruktoren werden entsprechend der Vererbungshierarchie verkettet. Die OnCreated-Methode wird für jeden Typ aufgerufen und kann angepasst werden.

  • Wenn eine Klasse in der Entitätshierarchie im Serverprojekt ausgelassen wird, wird sie auch in der generierten Entitätshierarchie im Clientprojekt ausgelassen. Den bekannten Typen in der Hierarchie, die von einer ausgelassenen Klasse abgeleitet sind, wird der entsprechende verfügbar gemachte Basisentitätstyp erneut als übergeordnetes Element zugewiesen, und alle öffentlichen Eigenschaften der ausgelassenen Klasse werden in den entsprechenden abgeleiteten Typen generiert.

  • Die generierten Entitätsklassen sind nicht sealed, um die Vererbung zwischen generierten Entitätsklassen zuzulassen. Das manuelle Erstellen von Klassen, die von einer generierten Entitätsklasse abgeleitet sind, wird nicht unterstützt.

Laufzeitverhalten

Die folgenden Regeln gelten zur Laufzeit für die Entitäten.

  • Die für die LINQ-Abfrageübersetzung unterstützten Abfrageoperatoren und Frameworkmethoden ändern sich nicht durch die Vererbung.

  • Instanzen bekannter Typen werden entsprechend den spezifischen Typen für Abfrage- und Sendevorgänge serialisiert und deserialisiert. Für Abfragevorgänge werden sie im polymorphen EntitySet akkumuliert.

  • Der Schlüssel und Typ für eine Instanz kann sich innerhalb eines einzelnen SubmitChanges-Vorgangs nicht ändern. Ein Customer kann z. B. nicht in einen PrivateSectorCustomer konvertiert werden. Sie können einen Typ konvertieren, indem Sie eine Instanz in einem SubmitChanges-Vorgang löschen und in einem anderen SubmitChanges-Vorgang eine neue Instanz erstellen.