Condividi tramite


Ereditarietà nei modelli dati

WCF RIA Services consente di utilizzare le entità che fanno parte di una gerarchia di ereditarietà. Un modello di ereditarietà include una classe di dati derivata da un'altra classe di dati. Ad esempio, un modello di ereditarietà polimorfico potrebbe contenere un'entità Customer e altre due entità, PublicSectorCustomer e PrivateSectorCustomer, che derivano da Customer. Con RIA Services è possibile scrivere metodi di query nel servizio del dominio che restituiscono una raccolta di tipi radice e di altri tipi che derivano dal tipo radice. In alternativa, è possibile scrivere un metodo di query che restituisce solo una raccolta di tipi derivati. È inoltre possibile scrivere metodi di modifica dei dati che agiscono su un tipo radice o su uno dei tipi derivati.

Modello di dati

Le classi di dati per il modello di ereditarietà vengono definite nel progetto server nello stesso modo in cui vengono definite tutte le classi di dati. Il modello a oggetti utilizzato può essere rappresentato da classi generate automaticamente dal livello di accesso ai dati o da classi di dati create manualmente.

Non è necessario esporre l'intera gerarchia tramite il servizio del dominio. In realtà, la classe meno derivata nella gerarchia esposta da un servizio del dominio viene considerata il tipo radice per le interazioni dal client. Anche i tipi che derivano dal tipo radice possono essere esposti al client. È necessario includere nell'attributo KnownTypeAttribute della classe radice tutti i tipi derivati che si desidera esporre. È possibile omettere i tipi derivati non includendoli nell'attributo KnownTypeAttribute, ma sarà necessario assicurarsi che non venga restituita alcuna istanza dei tipi omessi da una query. Nell'esempio seguente viene illustrato un modello di dati creato manualmente che include la classe base Customer e due classi derivate, PrivateSectorCustomer e PublicSectorCustomer. Customer sarà il tipo radice per le operazioni sui dati. Pertanto, le due classi derivate vengono incluse nell'attributo KnownTypeAttribute per la classe Customer come indicato utilizzando i parametri GetType dell'attributo.

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

Query polimorfiche

Dopo aver definito il modello di dati, creare un servizio del dominio che espone i tipi al client. Quando si espone un tipo in un metodo di query, è possibile restituire tale tipo e tutti i tipi derivati. Ad esempio, una query che restituisce una raccolta di entità Customer può includere gli oggetti PrivateSectorCustomer e gli oggetti PublicSectorCustomer. È inoltre possibile specificare che un metodo di query restituisca solo un tipo derivato. Nell'esempio seguente vengono illustrati i metodi di query che restituiscono ognuno dei tre tipi.

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

Codice generato per il progetto client

Quando si compila la soluzione, il codice viene generato nel progetto client per la gerarchia di ereditarietà esposta nel servizio del dominio. La classe radice della gerarchia viene generata e deriva dalla classe Entity. Ogni classe derivata viene generata e deriva dalla rispettiva classe base. Nella classe DomainContext viene generata una sola proprietà EntitySet che accetta gli oggetti del tipo radice. Per ogni query viene generato un oggetto EntityQuery che restituisce il tipo specificato nell'operazione del servizio del dominio.

Nell'esempio seguente viene illustrata una versione semplificata del codice generato nel progetto client per i metodi di query e le classi di dati illustrati negli esempi precedenti. L'esempio non include tutto il codice presente nelle classi generate ed è finalizzato a evidenziare solo alcune proprietà e alcuni metodi importanti.

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

Modifiche dei dati

È inoltre possibile aggiungere i metodi del servizio del dominio per l'aggiornamento, l'inserimento e l'eliminazione degli oggetti nella gerarchia di ereditarietà. Analogamente ai metodi di query, è possibile specificare un tipo radice o un tipo derivato per le operazioni. Tuttavia, qualsiasi operazione di aggiornamento, inserimento o eliminazione abilitata in un tipo derivato deve essere abilitata anche nel tipo radice. È inoltre possibile aggiungere i metodi di aggiornamento denominati per qualsiasi tipo nella gerarchia. Il metodo di aggiornamento denominato corrispondente nel client viene generato per il tipo specificato nel metodo.

Ogni volta che il client invia le modifiche al server per l'elaborazione, la versione più derivata del rispettivo metodo di inserimento, aggiornamento o eliminazione viene eseguita oggetto per oggetto. È necessario scegliere l'operazione da eseguire nei metodi dei tipi derivati, incluso se chiamare i metodi del tipo radice corrispondenti.

Nell'esempio seguente viene illustrata la firma per due metodi di aggiornamento e un metodo di aggiornamento denominato. Non viene illustrato il codice per implementare la logica di aggiornamento dei valori.

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

Associazioni

Un'associazione può essere definita nella classe radice o in una delle classi derivate dalla classe base. È possibile applicare l'attributo AssociationAttribute per definire un'associazione tra due classi di dati. Nell'esempio del modello di dati viene definita un'associazione tra Customer e Order. Quando un'associazione viene applicata a un tipo radice, tale associazione viene inclusa anche in tutti i tipi derivati.

Ai tipi derivati è possibile applicare ulteriori associazioni che non sono disponibili nel tipo radice.

Regole generali per l'utilizzo dell'ereditarietà

Per utilizzare l'ereditarietà con Servizi RIA, è necessario attenersi alle regole seguenti.

  • L'ereditarietà è supportata solo per i tipi di entità. I tipi di non entità vengono considerati come il tipo specificato nella firma dell'operazione del servizio del dominio.

  • I tipi di interfaccia non sono supportati per i parametri o i valori restituiti nelle operazioni del servizio del dominio.

  • Il set di tipi in una gerarchia di ereditarietà deve essere noto al momento della generazione del codice. Il comportamento per la restituzione di un tipo non specificato al momento della generazione del codice non è specificato e dipende dall'implementazione.

  • Il modificatore virtual nelle proprietà e nei campi pubblici per un tipo di entità è consentito ma viene ignorato durante la generazione del tipo di entità corrispondente nel client.

  • Gli overload del metodo per le operazioni del servizio del dominio non sono consentiti.

  • Le parole chiave new (C#) e Shadows (Visual Basic) nelle proprietà pubbliche non sono consentite nei tipi di entità e restituiranno un errore quando viene generato il codice client.

  • Le funzionalità di query LINQ correlate all'ereditarietà non possono essere convertite per l'esecuzione dei metodi del servizio del dominio. In particolare, non sono supportati i metodi e gli operatori OfType<T>, is, as e GetType(). Tuttavia, questi operatori possono essere utilizzati direttamente su EntitySet o EntityCollection nelle query LINQ to Objects.

Gerarchia di ereditarietà dell'entità

Per definire la gerarchia di ereditarietà, è necessario attenersi alle regole seguenti.

  • Specificare tutti i tipi di entità derivati noti esposti tramite un servizio del dominio utilizzando l'attributo System.Runtime.Serialization.KnownTypeAttribute.

  • I tipi noti nella gerarchia devono essere specificati nel tipo radice della gerarchia esposto tramite un servizio del dominio.

  • Ogni classe nel set di tipi noti deve essere public.

  • Una o più classi nella gerarchia possono essere abstract.

  • È possibile omettere una o più classi nella gerarchia quando si dichiarano i tipi noti. Le classi che derivano da una classe omessa vengono rese bidimensionali e ad esse viene assegnato un nuovo elemento padre nella gerarchia di ereditarietà in base alla classe padre successiva più elevata presente nella dichiarazione dei tipi noti. Le proprietà delle classi omesse vengono generate automaticamente in tutti i tipi esposti da esse derivati.

  • La classe radice deve disporre di una o più proprietà contrassegnate con l'attributo KeyAttribute. È possibile applicare l'attributo a una proprietà definita in un tipo di base non esposto del tipo radice. Una proprietà dell'entità pubblica in una classe di entità omessa nella gerarchia viene generata automaticamente in un tipo di entità esposto da essa derivato.

  • La dichiarazione e l'utilizzo delle associazioni restano invariati.

Operazioni DomainService

Per definire le operazioni del servizio del dominio nelle entità di una gerarchia di ereditarietà, è necessario attenersi alle regole seguenti.

  • È necessario che sia presente almeno un metodo di query che corrisponde al tipo radice nella gerarchia. È possibile che altre operazioni di query utilizzino un tipo più derivato per il valore restituito.

  • È possibile che le operazioni di query restituiscano un tipo radice per i metodi che restituiscono risultati polimorfici.

  • Se per un tipo della gerarchica è definita un'operazione di aggiornamento, inserimento o eliminazione, è necessario definire la stessa operazione per il tipo radice nella gerarchia. Non è possibile scegliere in modo selettivo un'operazione solo per alcuni tipi nella gerarchia.

  • Le operazioni personalizzate possono utilizzare un tipo radice o un tipo derivato per l'argomento di entità. Se il tipo effettivo di un'istanza è derivato dal tipo dell'operazione personalizzata, l'operazione è consentita.

  • La classe DomainServiceDescription restituisce il metodo di aggiornamento, inserimento o eliminazione più pertinente per un tipo specificato e tutti i metodi di query applicabili.

TypeDescriptionProvider

Per l'oggetto TypeDescriptionProvider (TDP) è necessario attenersi alle regole seguenti.

  • Se la classe radice nella gerarchia viene esposta tramite un metodo di query o un attributo IncludeAttribute, l'oggetto TypeDescriptionProvider per LINQ to SQL e Entity Framework deduce automaticamente le dichiarazioni di attributo KnownTypeAttribute per le entità. Il tipo noto non viene dedotto se viene esposto solo un tipo derivato tramite un metodo di query o un attributo IncludeAttribute.

  • La finestra di dialogo Aggiungi una nuova classe DomainService non consente di selezionare i tipi di entità derivati. È necessario creare manualmente i metodi di query, inserimento, aggiornamento o eliminazione per i tipi derivati.

Codice generato

Per il codice generato nel progetto client per le entità in una gerarchia di ereditarietà è necessario attenersi alle regole seguenti.

  • Per ogni gerarchia di ereditarietà viene generata esattamente una sola classe EntitySet. Il parametro di tipo dell'oggetto EntitySet è il tipo radice nella gerarchia di ereditarietà nota.

  • Per ogni tipo noto nella gerarchia di ereditarietà viene generato un tipo di entità corrispondente.

  • I metodi personalizzati vengono generati nel tipo in cui viene specificato e sono disponibili per qualsiasi tipo derivato.

  • I costruttori vengono concatenati in base alla gerarchia di ereditarietà. Il metodo OnCreated viene chiamato per ogni tipo e può essere personalizzato.

  • Se una classe nella gerarchia di entità del progetto server viene omessa, viene omessa anche nella gerarchia di entità generata del progetto client. Ai tipi noti nella gerarchia che derivano da una classe omessa viene assegnato un nuovo elemento padre nel tipo di entità di base esposto appropriato e tutte le proprietà pubbliche della classe omessa vengono generate nei tipi derivati appropriati.

  • Le classi di entità generate non sono di tipo sealed per consentire l'ereditarietà tra classi di entità generate. La creazione manuale delle classi che derivano da una classe di entità generata non è supportata.

Comportamento in fase di esecuzione

Per le entità in fase di esecuzione è necessario attenersi alle regole seguenti.

  • L'ereditarietà non modifica il set di operatori di query e i metodi del framework supportati per la conversione di query LINQ.

  • Le istanze di tipi noti vengono serializzate e deserializzate in base ai relativi tipi specifici per le operazioni di query e di invio. Per le operazioni di query vengono accumulate nell'oggetto polimorfico EntitySet.

  • La chiave e il tipo di un'istanza non possono essere modificati nell'ambito di una singola operazione SubmitChanges. Ad esempio, non è possibile convertire Customer in PrivateSectorCustomer. È possibile convertire un tipo eliminando un'istanza in un'operazione SubmitChanges e creando una nuova istanza in un'altra operazione SubmitChanges.