Héritage dans les modèles de données

Les Services RIA WCF vous permettent d'utiliser des entités appartenant à une hiérarchie d'héritage. Un modèle d'héritage comprend une classe de données dérivée d'une autre classe de données. Par exemple, un modèle d'héritage polymorphe pourra contenir une Customer entité et deux autres entités, (PublicSectorCustomer et PrivateSectorCustomer) qui dérivent de Customer. Avec les Services RIA , vous pouvez écrire des méthodes de requête dans le service de domaine qui retournent une collection de types racine et d'autres types qui dérivent du type racine. Vous pouvez aussi écrire une méthode de requête qui ne retourne qu'une collection de types dérivés. Vous pouvez également écrire des méthodes de modification des données qui opèrent sur un type racine ou sur chacun des types dérivés.

Modèle de données

Dans le projet serveur, les classes de données du modèle d'héritage se définissent exactement comme des classes de données ordinaires. Le modèle d'objet que vous utilisez peut être une classe générée automatiquement à partir de la couche Data Access ou une classe de données créée manuellement.

Vous n'avez pas besoin d'exposer toute la hiérarchie via votre service de domaine. Au lieu de cela, la classe la moins dérivée dans la hiérarchie, exposée par un service de domaine, est considérée comme étant le type racine pour les interactions venant du client. Les types qui dérivent du type racine peuvent aussi être exposés au client. Sur la classe racine, vous devez inclure dans l'attribut KnownTypeAttribute chacun des types dérivés que vous voulez exposer. Vous pouvez omettre des types dérivés en ne les incluant pas dans l'attribut KnownTypeAttribute, mais vous devrez alors vous assurer que vous ne retournez aucune instance de ces types omis dans une requête. L'exemple suivant montre un modèle de données créé manuellement qui comprend la classe de base Customer et deux classes dérivées, PrivateSectorCustomer et PublicSectorCustomer. Customer sera le type racine pour les opérations de données. Les deux classes dérivées sont donc incluses dans l'attribut KnownTypeAttribute de la classe Customer comme indiqué à l'aide des paramètres GetType de l'attribut.

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

Requêtes polymorphes

Après avoir défini le modèle de données, vous devez créer un service de domaine qui expose les types au client. Lorsque vous exposez un type dans une méthode de requête, vous pouvez retourner ce type et tous les types dérivés. Par exemple, une requête qui retourne une collection d'entités Customer peut inclure des objets PrivateSectorCustomer et des objets PublicSectorCustomer. Vous pouvez aussi spécifier qu'une méthode de requête ne retourne qu'un type dérivé. L'exemple suivant montre des méthodes de requête qui retournent chacun de ces trois types.

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

Code généré pour le projet client

Lorsque vous générez la solution, le code est généré dans le projet client pour la hiérarchie d'héritage que vous avez exposée dans le service de domaine. La classe racine de la hiérarchie est générée et dérive de la classe Entity. Chaque classe dérivée est générée et dérive de sa classe de base respective. Dans la classe DomainContext, seule une propriété EntitySet unique est générée et elle prend des objets du type racine. Un objet EntityQuery est généré pour chaque requête et retourne le type spécifié dans l'opération de service de domaine.

L'exemple suivant montre une version simplifiée du code généré dans le projet client pour les méthodes de requête et les classes de données montrées dans les exemples précédents. Il n'inclut pas la totalité du code présent dans les classes générées et ne sert qu'à mettre en avant certaines propriétés et méthodes importantes.

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

Modifications de données

Vous pouvez aussi ajouter des méthodes de service de domaine pour la mise à jour, l'insertion et la suppression d'objets dans la hiérarchie d'héritage. Tout comme avec les méthodes de requête, vous pouvez spécifier un type racine ou un type dérivé pour les opérations. Cependant, toute opération de mise à jour, d'insertion ou de suppression activée sur un type dérivé doit aussi être activée sur le type racine. Vous pouvez aussi ajouter des méthodes de mise à jour personnalisée pour n'importe quel type dans la hiérarchie. La méthode de mise à jour personnalisée correspondante sur le client est générée pour le type spécifié dans la méthode.

Chaque fois que le client soumet des modifications au serveur pour traitement, la version la plus dérivée de la méthode respective d'insertion, de mise à jour ou de suppression est exécutée objet par objet. Vous devez choisir quoi faire dans les méthodes de type dérivé, notamment décider si vous appelez les méthodes de type racine correspondantes.

L'exemple suivant montre la signature de deux méthodes de mise à jour et d'une méthode de mise à jour personnalisée. Il ne montre pas le code permettant d'implémenter la logique de mise à jour des valeurs.

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 */ }

Associations

Une association peut être définie dans la classe racine ou dans l'une des classes dérivées de la classe de base. Vous devez appliquer l'attribut AssociationAttribute pour définir une association entre deux classes de données. Dans l'exemple de modèle de données, une association est définie entre Customer et Order. Quand une association est appliquée à un type racine, tous les types dérivés contiennent aussi cette association.

Vous pouvez appliquer des associations supplémentaires aux types dérivés qui ne sont pas disponibles sur le type racine.

Règles générales d'utilisation de l'héritage

Les règles suivantes s'appliquent à l'utilisation de l'héritage avec les Services RIA.

  • L'héritage n'est pris en charge que pour les types d'entité. Les types de non-entité sont traités comme le type spécifié dans la signature de l'opération de service de domaine.

  • Les types d'interface ne sont pas pris en charge pour les valeurs de retour ou les paramètres des opérations de service de domaine.

  • Le jeu de types d'une hiérarchie d'héritage doit être connu au moment de la génération du code. Le comportement lors du retour d'un type non spécifié au moment de la génération du code n'est pas spécifié et dépend de l'implémentation.

  • Le modificateur virtual des propriétés et des champs publics d'un type d'entité est autorisé, mais il est ignoré lors de la génération du type d'entité correspondant sur le client.

  • Les surcharges de méthode pour les opérations de service de domaine ne sont pas autorisées.

  • Les mots clés new (C#) et Shadows (Visual Basic) sur les propriétés publiques ne sont pas autorisés sur les types d'entité et provoqueront une erreur lors de la génération du code client.

  • Les fonctions de requête LINQ liées à l'héritage ne peuvent pas se traduire pour l'exécution de méthodes de service de domaine. En particulier, les opérateurs et les méthodes OfType<T>, is, aset GetType() ne sont pas pris en charge. Cependant, ces opérateurs peuvent être utilisés directement sur EntitySet ou EntityCollection dans des requêtes LINQ to Objects.

Hiérarchie d'héritage d'entité

Les règles suivantes s'appliquent à la définition de la hiérarchie d'héritage.

  • Vous devez spécifier tous les types d'entité dérivés connus que vous exposez via un service de domaine à l'aide de l'attribut System.Runtime.Serialization.KnownTypeAttribute.

  • Les types connus de la hiérarchie doivent être spécifiés sur le type racine de la hiérarchie qui est exposé via un service de domaine.

  • Chaque classe du jeu de types connus doit être public.

  • Une ou plusieurs classes de la hiérarchie peuvent être abstract.

  • Vous pouvez omettre une ou plusieurs classes de la hiérarchie lors de la déclaration de types connus. Les classes qui dérivent d'une classe omise sont aplaties et à nouveau apparentées dans la hiérarchie d'héritage selon la classe parent immédiatement supérieure dans la déclaration des types connus. Les propriétés des classes omises sont générées automatiquement sur tous les types exposés qui en dérivent.

  • La classe racine doit avoir une ou plusieurs propriétés marquées de l'attribut KeyAttribute. Vous pouvez appliquer l'attribut à une propriété définie dans un type de base non exposé de ce type racine. Une propriété d'entité publique sur une classe d'entité omise dans la hiérarchie est générée automatiquement sur un type d'entité exposé qui en dérive.

  • La déclaration et l'utilisation des associations ne changent pas.

Opérations DomainService

Les règles suivantes s'appliquent à la définition d'opérations de service de domaine sur les entités dans une hiérarchie d'héritage.

  • Au moins une méthode de requête doit correspondre au type racine dans la hiérarchie. Les opérations de requête supplémentaires peuvent utiliser un type plus dérivé pour la valeur de retour.

  • Les opérations de requête peuvent retourner un type racine pour les méthodes qui retournent des résultats polymorphes.

  • Si une opération de mise à jour, d'insertion ou de suppression est définie pour un des types de la hiérarchie, alors la même opération doit être définie pour le type racine de la hiérarchie. Il n'est pas possible d'opter sélectivement pour une opération pour certains types seulement de la hiérarchie.

  • Les opérations personnalisées peuvent utiliser un type racine ou un type dérivé pour l'argument d'entité. Lorsque le type réel d'une instance est dérivé du type de l'opération personnalisée, l'opération est autorisée.

  • La classe DomainServiceDescription retourne la méthode de mise à jour, d'insertion ou de suppression la plus applicable pour un type donné, ainsi que toutes les méthodes de requête applicables.

TypeDescriptionProvider

Les règles suivantes s'appliquent également à l'objet TypeDescriptionProvider (TDP).

  • Quand la classe racine de la hiérarchie est exposée via une méthode de requête ou un attribut IncludeAttribute, l'objet TypeDescriptionProvider pour LINQ to SQL et l'Entity Framework déduit automatiquement les déclarations d'attribut KnownTypeAttribute pour les entités. Le type connu n'est pas déduit lorsque seul un type dérivé est exposé via une méthode de requête ou un attribut IncludeAttribute.

  • La boîte de dialogue Ajouter une nouvelle classe de service de domaine ne permet pas de sélectionner des types d'entité dérivés. Vous devez créer manuellement les méthodes de requête, d'insertion, de mise à jour ou de suppression pour les types dérivés.

Code généré

Les règles suivantes s'appliquent au code généré dans le projet client pour les entités d'une hiérarchie d'héritage.

  • Une classe EntitySet exactement est générée pour chaque hiérarchie d'héritage. Le paramètre de type de l'objet EntitySet est le type racine de la hiérarchie d'héritage connue.

  • Pour chaque type connu dans la hiérarchie d'héritage, un type d'entité correspondant est généré.

  • Les méthodes personnalisées sont générées sur le type à l'endroit où il est spécifié et sont disponibles pour tous les types dérivés.

  • Les constructeurs sont chaînés en fonction de la hiérarchie d'héritage. La méthode OnCreated est appelée pour chaque type et peut être personnalisée.

  • Si une classe de la hiérarchie d'entité du projet serveur est omise, elle est également omise dans la hiérarchie d'entité générée dans le projet client. Les types connus de la hiérarchie qui dérivent d'une classe omise sont à nouveau apparentés au type d'entité de base exposé approprié, et les éventuelles propriétés publiques de la classe omise sont générées dans les types dérivés appropriés.

  • Les classes d'entité générées ne sont pas sealed afin d'autoriser les héritages entre elles. La création manuelle de classes qui dérivent d'une classe d'entité générée n'est pas prise en charge.

Comportement à l'exécution

Les règles suivantes s'appliquent aux entités pendant l'exécution.

  • L'héritage ne modifie pas le jeu d'opérateurs de requête et de méthodes de structure pris en charge pour la traduction des requêtes LINQ.

  • Les instances de types connus sont sérialisées et désérialisées d'après leurs types spécifiques pour les opérations de requête et d'envoi. Pour les opérations de requête, elles sont cumulées dans l'objet EntitySet polymorphe.

  • La clé et le type d'une instance ne peuvent pas changer dans le cadre d'une opération SubmitChanges unique. Par exemple, un Customer ne peut pas être converti en PrivateSectorCustomer. Vous pouvez convertir un type en effaçant une instance dans une opération SubmitChanges et en créant une nouvelle instance dans une autre opération SubmitChanges.