Herencia en modelos de datos

WCF RIA Services permite trabajar con entidades que forman parte de una jerarquía de herencia. Un modelo de herencia incluye una clase de datos que se deriva de otra clase de datos. Por ejemplo, un modelo de herencia polimórfico puede contener una entidad Customer y otras dos entidades, (PublicSectorCustomer y PrivateSectorCustomer), que deriven de Customer. Con RIA Services , puede escribir métodos de consulta en el servicio de dominio que devuelvan una colección de tipos raíz y otros tipos que deriven del tipo raíz. O bien, puede escribir un método de consulta que solamente devuelva una colección de los tipos derivados. También puede escribir métodos de modificación de datos que funcionen en un tipo raíz o en cualquiera de los tipos derivados.

Modelo de datos

En el proyecto de servidor, puede definir clases de datos propias para el modelo de herencia del mismo modo que definiría clases de datos. El modelo de objetos que utilice puede consistir en clases generadas automáticamente de la capa de acceso a datos o clases de datos creadas manualmente.

No es necesario exponer la jerarquía completa en todo su servicio de dominio. En su lugar, la menor clase derivada de la jerarquía expuesta por un servicio de dominio se considera el tipo raíz para las interacciones del cliente. Los tipos que derivan del tipo raíz también se pueden exponer al cliente. En la clase raíz, debe incluir en el atributo KnownTypeAttribute cualquiera de los tipos derivados que desee exponer. Puede omitir tipos derivados no incluyéndolos en el atributo KnownTypeAttribute, pero entonces debe asegurarse de que no devuelve instancias de esos tipos omitidos desde una consulta. En el ejemplo siguiente se muestra un modelo de datos creado manualmente que incluye la clase base Customer y dos clases derivadas, PrivateSectorCustomer y PublicSectorCustomer. Customer será el tipo raíz para las operaciones de datos. De este modo, las dos clases derivadas quedan incluidas en el atributo KnownTypeAttribute para la clase Customer como se indica utilizando los parámetros GetType del atributo.

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

Consultas polimórficas

Después de definir el modelo de datos, puede crear un servicio de dominio que exponga los tipos al cliente. Cuando se expone un tipo en un método de consulta, se puede devolver ese tipo y los tipos derivados. Por ejemplo, una consulta que devuelve una colección de entidades Customer puede incluir objetos PrivateSectorCustomer y objetos PublicSectorCustomer. También se puede especificar que un método de consulta solamente devuelva un tipo derivado. En el ejemplo siguiente se muestran métodos de consulta que devuelven cada uno de estos tres tipos.

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

Código generado para el proyecto de cliente

Cuando se genera la solución, se genera código en el proyecto de cliente para la jerarquía de herencia que se ha expuesto en el servicio de dominio. Se genera la clase raíz de la jerarquía y deriva de la clase Entity. Se genera cada clase derivada y deriva de su clase base respectiva. En la clase DomainContext, solo se genera una propiedad EntitySet y toma objetos del tipo raíz. Se genera un objeto EntityQuery para cada consulta y devuelve el tipo especificado en la operación de servicio de dominio.

En el ejemplo siguiente se muestra una versión simplificada del código que se genera en el proyecto de cliente para los métodos de consulta y las clases de datos que se muestran en los ejemplos anteriores. No se incluye todo el código que se encuentra en las clases generadas y solo se pretende resaltar algunos métodos y propiedades 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);
}

Modificaciones de datos

También puede agregar métodos de servicio de dominio para actualizar, insertar y eliminar objetos en la jerarquía de herencia. Al igual que con los métodos de consulta, puede especificar un tipo raíz o un tipo derivado para las operaciones. Sin embargo, las operaciones de actualización, inserción o eliminación habilitadas en un tipo derivado también se deben habilitar en el tipo raíz. También puede agregar los métodos de actualización con nombres para cualquier tipo de la jerarquía. Se genera el método de actualización con nombres correspondiente en el cliente para el tipo especificado en el método.

Cada vez que el cliente envía cambios al servidor para su procesamiento, se ejecuta objeto por objeto la versión derivada de mayor nivel del método respectivo de inserción, actualización o eliminación. Debe elegir lo que ha de realizar en los métodos de tipo derivado, incluso si llama a los métodos de tipo raíz correspondientes.

En el ejemplo siguiente se muestra la signatura para dos métodos de actualización y un método de actualización con nombres. No se muestra el código para implementar la lógica de actualización de los valores.

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

Asociaciones

Una asociación se puede definir en la clase raíz o en una de las clases derivadas de la clase base. Se aplica el atributo AssociationAttribute para definir una asociación entre dos clases de datos. En el ejemplo de modelo de datos, se define una asociación entre Customer y Order. Cuando se aplica una asociación a un tipo raíz, todos los tipos derivados también contienen esa asociación.

Puede aplicar asociaciones adicionales a los tipos derivados que no están disponibles en el tipo raíz.

Reglas generales para el uso de la herencia

Las siguientes reglas se aplican al uso de la herencia con RIA Services.

  • La herencia solo se admite para los tipos de entidad. Los tipos que no son de entidad se tratan como el tipo especificado en la signatura de operación de servicio de dominio.

  • Los tipos de interfaz no se admiten para valores devueltos o parámetros en operaciones de servicio de dominio.

  • El conjunto de tipos en una jerarquía de herencia se debe conocer en el momento de la generación de código. El comportamiento para devolver un tipo no especificado en el momento de la generación de código no está especificado y depende de la implementación.

  • Se permite el modificador virtual en propiedades y campos públicos para un tipo de entidad, pero se omite cuando se genera el tipo de entidad correspondiente en el cliente.

  • No se permiten sobrecargas de método para las operaciones de servicio de dominio.

  • Las palabras clave new (C#) y Shadows (Visual Basic) en propiedades públicas no se permiten en tipos de entidad y se producirá un error cuando se genere el código de cliente.

  • Las capacidades de consulta de LINQ relacionadas con la herencia no se pueden trasladar para la ejecución de métodos de servicio de dominio. Concretamente, no se admiten métodos ni operadores OfType<T>, is, as y GetType(). Sin embargo, estos operadores se pueden utilizar directamente en EntitySet o EntityCollection en LINQ para consultas de objetos.

Jerarquía de herencia de entidad

Las reglas siguientes se aplican a la definición de la jerarquía de herencia.

  • Debe especificar todos los tipos de entidad derivados conocidos que expone a través de un servicio de dominio utilizando el atributo System.Runtime.Serialization.KnownTypeAttribute.

  • Los tipos conocidos de la jerarquía se deben especificar en el tipo raíz de la jerarquía que se expone a través de un servicio de dominio.

  • Cada clase del conjunto de tipos conocido debe ser public.

  • Una o más clases de la jerarquía pueden ser abstract.

  • Puede omitir una o más clases de la jerarquía al declarar los tipos conocidos. Las clases que derivan de una clase omitida pasan a la condición de planas y vuelven a ser primarias en la jerarquía de herencia basándose en la clase primaria más alta siguiente que está en la declaración de tipos conocidos. Las propiedades de las clases omitidas se generan automáticamente en los tipos expuestos que derivan de ellas.

  • La clase raíz debe tener una o más propiedades marcadas con el atributo KeyAttribute. Puede aplicar el atributo a una propiedad definida en un tipo base no expuesto de ese tipo raíz. Una propiedad de entidad pública en una clase de entidad omitida de la jerarquía se genera automáticamente en un tipo de entidad expuesto que deriva de ella.

  • La declaración y el uso de asociaciones no cambian.

Operaciones de DomainService

Las reglas siguientes se aplican a la definición de operaciones de servicio de dominio en entidades de una jerarquía de herencia.

  • Debe haber por lo menos un método de consulta que corresponda al tipo raíz de la jerarquía. Las operaciones de consulta adicionales pueden utilizar un tipo más derivado para el valor devuelto.

  • Las operaciones de la consulta pueden devolver un tipo raíz para los métodos que devuelven resultados polimórficos.

  • Si se define una operación de actualización, inserción o eliminación para cualquier tipo de la jerarquía, se debe definir la misma operación para el tipo raíz de la jerarquía. No es posible optar selectivamente por una operación para solo ciertos tipos de la jerarquía.

  • Las operaciones personalizadas pueden utilizar un tipo raíz o un tipo derivado para el argumento de entidad. Cuando el tipo real de una instancia se deriva del tipo en la operación personalizada, se permite la operación.

  • La clase DomainServiceDescription devuelve el método de actualización, inserción o eliminación más aplicable para un tipo determinado y todos los métodos de consulta aplicables.

TypeDescriptionProvider

Las reglas siguientes se aplican a TypeDescriptionProvider (TDP).

  • Cuando la clase raíz de la jerarquía se expone a través de un método de consulta o un atributo IncludeAttribute, TypeDescriptionProvider para LINQ to SQL y Entity Framework deducen automáticamente las declaración del atributo KnownTypeAttribute para entidades. El tipo conocido no se deduce cuando se expone solo un tipo derivado a través de un método de consulta o el atributo IncludeAttribute.

  • El cuadro de diálogo Agregar nueva clase de servicio de dominio no permite la selección de tipos de entidad derivados. Debe crear manualmente los métodos de consulta, inserción, actualización o eliminación para los tipos derivados.

Código generado

Las reglas siguientes se aplican al código generado en el proyecto de cliente para las entidades de una jerarquía de herencia.

  • Se genera exactamente una clase EntitySet para cada jerarquía de herencia. El parámetro de tipo de EntitySet es el tipo raíz de la jerarquía de herencia conocida.

  • Para cada tipo conocido de la jerarquía de herencia, se genera un tipo de entidad correspondiente.

  • Los métodos personalizados se generan en el tipo en que se especifican, y están disponibles para los tipos derivados.

  • Los constructores se encadenan de acuerdo con la jerarquía de herencia. Se llama al método OnCreated para cada tipo y se puede personalizar.

  • Si se omite una clase de la jerarquía de entidad en el proyecto de servidor, también se omite en la jerarquía de entidad generada en el proyecto de cliente. Los tipos conocidos de la jerarquía que derivan de una clase omitida vuelven a ser primarios en el tipo de entidad base expuesto apropiado y las propiedades públicas de la clase omitida se generan en los tipos derivados apropiados.

  • Las clases de entidad generadas no son de tipo sealed para permitir la herencia entre ellas. No se admite la creación manual de clases que derivan de una clase de entidad generada.

Comportamiento en tiempo de ejecución

Las reglas siguientes se aplican a las entidades en tiempo de ejecución.

  • La herencia no cambia el conjunto de operadores de consulta y métodos de marco que se admiten para la conversión de consultas de LINQ.

  • Las instancias de tipos conocidos se serializan y deserializan de acuerdo con sus tipos específicos para las operaciones de consulta y envío. Para las operaciones de consulta, se acumulan en el elemento EntitySet polimórfico.

  • La clave y el tipo para una instancia no puede cambiar dentro del ámbito de una operación SubmitChanges única. Por ejemplo, un elemento Customer no se puede convertir en un elemento PrivateSectorCustomer. Puede convertir un tipo eliminando una instancia en una operación de SubmitChanges y creando una nueva instancia en otra operación de SubmitChanges.