Inheritance in Data Models

[ This document was written for WCF Services Version 1 Service Pack 2 and might not be up to date Please see Release Notes or Changelog for a list of changes since WCF RIA Services ]

Open Ria Services enables you to work with entities that are part of an inheritance hierarchy. An inheritance model includes a data class that is derived from another data class. For example, a polymorphic inheritance model could contain a Customer entity and two other entities, (PublicSectorCustomer and PrivateSectorCustomer), that derive from Customer. With Open Ria Services, you can write query methods in the domain service that return a collection of root types and other types that derive from the root type. Or, you can write a query method that returns a collection only of the derived types. You can also write data modification methods that operate on either a root type or on any of the derived types.

Data Model

In the server project, you define your data classes for the inheritance model just as you would define any data classes. The object model you use can either be automatically generated classes from the data access layer or manually-created data classes.

You do not need to expose the entire hierarchy through your domain service. Instead, the least derived class in the hierarchy that is exposed by a domain service is considered the root type for interactions from the client. Types that derive from the root type can also be exposed to the client. On the root class, you must include in the KnownTypeAttribute attribute any of the derived types that you want to expose. You can omit derived types by not including them in the KnownTypeAttribute attribute, but then you must ensure you do not return any instances of those omitted types from a query. The following example shows a manually-created data model that includes the base class Customer, and two derived classes, PrivateSectorCustomer and PublicSectorCustomer. Customer will be the root type for data operations. So the two derived classes are included in the KnownTypeAttribute attribute for the Customer class as indicated using the attribute’s GetType parameters.

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

Polymorphic Queries

After defining the data model, you create a domain service that exposes the types to the client. When you expose a type in a query method, you can return that type and any derived types. For example, a query that returns a collection of Customer entities can include PrivateSectorCustomer objects and PublicSectorCustomer objects. You can also specify that a query method only return a derived type. The following example shows query methods that return each of these three 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);
}

Generated Code for the Client Project

When you build the solution, code is generated in the client project for the inheritance hierarchy you have exposed in the domain service. The root class of the hierarchy is generated and derives from the Entity class. Each derived class is generated and derives from its respective base class. In the DomainContext class, only a single EntitySet\ property is generated and it takes objects of the root type. An EntityQuery object is generated for each query and it returns the type specified in the domain service operation.

The following example shows a simplified version of the code that is generated in the client project for the query methods and data classes shown in the previous examples. It does not include all of the code that is present in the generated classes, and is intended only to highlight a few important properties and methods.

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

Data Modifications

You can also add domain service methods for updating, inserting, and deleting objects in the inheritance hierarchy. As with query methods, you can specify either a root type or a derived type for the operations. However, any update, insert, or delete operation enabled on a derived type must also be enabled on the root type. You can also add named update methods for any type in the hierarchy. The corresponding named update method on the client is generated for the type specified in the method.

Whenever the client submits changes to the server for processing, the most derived version of the respective insert, update, or delete method is executed on an object-by-object basis. You must choose what to do in the derived-type methods, including whether you call the corresponding root type methods.

The following example shows the signature for two update methods and one named update method. It does not show the code to implement the logic of updating the values.

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

An association can be defined in the root class or in one of the classes derived from the base class. You apply the AssociationAttribute attribute to define an association between two data classes. In the data model example, an association is defined between Customer and Order. When an association is applied to a root type, all derived types also contain that association.

You can apply additional associations to derived types that are not available on the root type.

General Rules for using Inheritance

The following rules apply to using inheritance with Open Ria Services.

  • Inheritance is supported only for entity types. Non-entity types are treated as the type specified in the domain service operation signature.

  • Interface types are not supported for return values or parameters in domain service operations.

  • The set of types in an inheritance hierarchy must be known at the time of code generation. The behavior for returning a type not specified at the time of code generation is unspecified and implementation-dependent.

  • The virtual modifier on public properties and fields for an entity type is permitted but ignored when generating the corresponding entity type on the client.

  • Method overloads for domain service operations are not permitted.

  • The new (C#) and Shadows (Visual Basic) keywords on public properties are not allowed on entity types and will result in an error when client code is generated.

  • LINQ query capabilities related to inheritance cannot be translated for execution of domain service methods. Specifically, OfType\, is, as, and GetType() operators and methods are not supported. However, these operators may be used directly against EntitySet or EntityCollection\ in LINQ to Objects queries.

Entity Inheritance Hierarchy

The following rules apply to defining the inheritance hierarchy.

  • You specify all known derived entity types that you expose through a domain service by using the System.Runtime.Serialization.KnownTypeAttribute attribute.

  • Known types in the hierarchy must be specified on the root type in the hierarchy that is exposed through a domain service.

  • Each class in the known types set must be public.

  • One or more classes in the hierarchy may be abstract.

  • You can omit one or more classes in the hierarchy when declaring known types. The classes that derive from an omitted class are flattened and re-parented in the inheritance hierarchy based on the next higher parent class that is in the known types declaration. Properties from the omitted classes are automatically generated on any exposed types that derive from them.

  • The root class must have one or more properties marked with the KeyAttribute attribute. You can apply the attribute to a property that is defined in an unexposed base type of that root type. A public entity property on an omitted entity class in the hierarchy is automatically generated on an exposed entity type that derives from it.

  • The declaration and use of associations is unchanged.

DomainService Operations

The following rules apply to defining domain service operations on entities in an inheritance hierarchy.

  • There must be at least one query method corresponding to the root type in the hierarchy. Additional query operations may use a more derived type for the return value.

  • Query operations may return a root type for methods which return polymorphic results.

  • If an update, insert, or delete operation is defined for any type in the hierarchy, then the same operation must be defined for the root type in the hierarchy. It is not possible to selectively opt into an operation for only certain types in the hierarchy.

  • Custom operations may use a root type or a derived type for the entity argument. When the actual type of an instance is derived from the type in the custom operation, the operation is permitted.

  • The DomainServiceDescription class returns the most applicable update, insert, or delete method for a given type and all applicable query methods.

TypeDescriptionProvider

The following rules apply to the TypeDescriptionProvider (TDP).

  • When the root class in the hierarchy is exposed through a query method or an IncludeAttribute attribute, the TypeDescriptionProvider for LINQ to SQL and the Entity Framework automatically infers KnownTypeAttribute attribute declarations for entities. The known type is not inferred when only a derived type is exposed through a query method or IncludeAttribute attribute.

  • The Add New Domain Service Class dialog box does not allow derived entity types to be selected. You must manually create the query, insert, update, or delete methods for the derived types.

Generated Code

The following rules apply to the code generated in the client project for entities in an inheritance hierarchy.

  • Exactly one EntitySet\ class is generated for each inheritance hierarchy. The type parameter of the EntitySet\ is the root type in the known inheritance hierarchy.

  • For each known type in the inheritance hierarchy, a corresponding entity type is generated.

  • Custom methods are generated on the type where it is specified, and are available to any derived types.

  • The constructors are chained according to the inheritance hierarchy. The OnCreated method is called for each type and may be customized.

  • If a class in the entity hierarchy in the server project is omitted, it is also omitted in the generated entity hierarchy in the client project. The known types in the hierarchy that derive from an omitted class are re-parented to the appropriate exposed base entity type, and any public properties from the omitted class are generated in the appropriate derived types.

  • The generated entity classes are not sealed to permit inheritance between generated entity classes. Manually creating classes that derive from a generated entity class is unsupported.

Runtime Behavior

The following rules apply to the entities at runtime.

  • Inheritance does not change the set of query operators and framework methods that are supported for LINQ query translation.

  • Instances of known types are serialized and deserialized according to their specific types for query and submit operations. For query operations, they are accumulated in the polymorphic EntitySet\.

  • The key and type for an instance cannot change within the scope of a single SubmitChanges operation. For example, a Customer cannot be converted to a PrivateSectorCustomer. You can convert a type by deleting an instance in one SubmitChanges operation and creating a new instance in another SubmitChanges operation.

Last updated