次の方法で共有


データ モデルでの継承

WCF RIA サービス では、継承階層の一部であるエンティティを操作できます。継承モデルには、別のデータ クラスから派生するデータ クラスが含まれます。たとえば、ポリモーフィックな継承モデルには、Customer エンティティと、Customer から派生する他の 2 つのエンティティ (PublicSectorCustomer および PrivateSectorCustomer) が含まれる場合があります。RIA サービス を使用すると、ルート型とルート型から派生する他の型のコレクションを返す、ドメイン サービスのクエリ メソッドを記述できます。または、派生型のみのコレクションを返すクエリ メソッドを記述できます。ルート型またはいずれかの派生型に対して動作するデータ変更メソッドを記述することもできます。

データ モデル

サーバー プロジェクトでは、任意のデータ クラスを定義するのと同じように継承モデルのデータ クラスを定義します。使用するオブジェクト モデルは、データ アクセス層から自動的に生成されるクラスにすることも、手動で作成するデータ クラスにすることもできます。

ドメイン サービスを介して階層全体を公開する必要はありません。代わりに、ドメイン サービスによって公開される階層内の最低派生クラスは、クライアントからの対話のルート型と見なされます。ルート型から派生する型をクライアントに公開することもできます。ルート クラスでは、公開するいずれかの派生型を KnownTypeAttribute 属性に含める必要があります。KnownTypeAttribute 属性に派生型を含めないことで派生型を省略できますが、その場合は、省略した型のインスタンスがクエリから返されないようにする必要があります。基本クラス (Customer) と 2 つの派生クラス (PrivateSectorCustomer および PublicSectorCustomer) を含む手動で作成されたデータ モデルを次の例に示します。Customer は、データ操作のルート型になります。そのため、例に示しているように、Customer クラスの KnownTypeAttribute 属性の GetType パラメーターを使用して、この属性に 2 つの派生クラスを含めます。

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

ポリモーフィック クエリ

データ モデルを定義した後、クライアントに型を公開するドメイン サービスを作成します。クエリ メソッドの型を公開する場合、その型とすべての派生型を返すことができます。たとえば、Customer エンティティのコレクションを返すクエリに、PrivateSectorCustomer オブジェクトと PublicSectorCustomer オブジェクトを含めることができます。また、クエリ メソッドから派生型のみが返されるように指定することもできます。これらの 3 つそれぞれの型を返すクエリ メソッドを次の例に示します。

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

クライアント プロジェクトの生成されたコード

ソリューションをビルドすると、ドメイン サービスで公開した継承階層に対してクライアント プロジェクトでコードが生成されます。階層のルート クラスが生成され、Entity クラスから派生します。各派生クラスが生成され、それぞれの基本クラスから派生します。DomainContext クラスでは、1 つの EntitySet プロパティのみが生成され、このプロパティはルート型のオブジェクトを受け取ります。クエリごとに EntityQuery オブジェクトが生成され、このオブジェクトはドメイン サービス操作で指定されている型を返します。

前の例で示したクエリ メソッドとデータ クラスに対してクライアント プロジェクトで生成されるコードを簡略化したものを次の例に示します。この例には、生成されたクラスに存在するすべてのコードが含まれているわけではなく、いくつかの重要なプロパティとメソッドを強調することのみを目的としています。

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

データ変更

継承階層内のオブジェクトを更新、挿入、および削除するためのドメイン サービス メソッドを追加することもできます。クエリ メソッドと同様に、各操作のルート型または派生型を指定できます。ただし、派生型で有効になっている更新、挿入、または削除操作は、ルート型でも有効にする必要があります。階層内の任意の型に対する名前付き更新メソッドを追加することもできます。メソッドで指定された型に対して、対応する名前付き更新メソッドがクライアントで生成されます。

変更が処理のためにクライアントからサーバーに送信されるたびに、挿入、更新、または削除メソッドの最多派生メソッドがオブジェクトごとに実行されます。対応するルート型のメソッドを呼び出すかどうかなど、派生型のメソッドで実行する処理を選択する必要があります。

2 つの更新メソッドと 1 つの名前付き更新メソッドのシグネチャを次の例に示します。値の更新ロジックを実装するコードは含まれません。

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

関連付け

関連付けは、ルート クラス、または基本クラスから派生するいずれかのクラスで定義できます。AssociationAttribute 属性を適用して、2 つのデータ クラス間の関連付けを定義します。データ モデルの例では、CustomerOrder 間の関連付けが定義されています。ルート型に関連付けを適用すると、すべての派生型にもその関連付けが含まれます。

ルート型で使用できない派生型に追加の関連付けを適用できます。

継承の使用に関する一般的な規則

RIA Services での継承の使用には、次の規則が適用されます。

  • 継承はエンティティ型でのみサポートされています。エンティティ型以外の型は、ドメイン サービス操作シグネチャに指定された型として処理されます。

  • ドメイン サービス操作の戻り値またはパラメーターでは、インターフェイス型がサポートされていません。

  • 継承階層内の型のセットは、コード生成時に認識されている必要があります。コード生成時に指定されていない型を返す動作は、指定されておらず、実装に依存します。

  • エンティティ型のパブリック プロパティとパブリック フィールドの virtual 修飾子は許可されていますが、対応するエンティティ型がクライアントで生成されるときは無視されます。

  • ドメイン サービス操作に対するメソッドのオーバーロードは許可されていません。

  • パブリック プロパティの new (C#) キーワードと Shadows (Visual Basic) キーワードはエンティティ型では使用できません。クライアント コードの生成時にエラーが発生します。

  • ドメイン サービス メソッドを実行するために、継承に関連する LINQ クエリ機能を変換することはできません。具体的には、OfType<T>isas、および GetType() の各演算子とメソッドは、サポートされていません。ただし、これらの演算子は、LINQ to Objects クエリの EntitySet または EntityCollection に対して直接使用できます。

エンティティ継承階層

継承階層の定義には、次の規則が適用されます。

  • System.Runtime.Serialization.KnownTypeAttribute 属性を使用して、ドメイン サービスを介して公開する既知の派生エンティティ型をすべて指定します。

  • ドメイン サービスを介して公開される階層内のルート型で、階層内の既知の型を指定する必要があります。

  • 既知の型セットの各クラスは、public である必要があります。

  • 階層内の 1 つ以上のクラスを abstract にすることができます。

  • 既知の型を宣言する場合は、階層内の 1 つ以上のクラスを省略できます。省略したクラスから派生するクラスは平坦化され、既知の型宣言に含まれている 1 つ下位の親クラスに基づいて継承階層内で親が再指定されます。省略したクラスのプロパティは、それらのクラスから派生する、公開されるすべての型に対して自動的に生成されます。

  • ルート クラスには、KeyAttribute 属性でマークされた 1 つ以上のプロパティがある必要があります。この属性は、そのルート型の非公開の基本型で定義されているプロパティに適用できます。階層内の省略されたエンティティ クラスのパブリック エンティティ プロパティは、派生元の公開されるエンティティ型に対して自動的に生成されます。

  • 関連付けの宣言と使用は変更されません。

DomainService 操作

継承階層内のエンティティに対するドメイン サービス操作の定義には、次の規則が適用されます。

  • 階層内のルート型に対応するクエリ メソッドが、少なくとも 1 つ存在する必要があります。追加のクエリ操作で、戻り値のより強い派生型を使用できます。

  • クエリ操作によって、さまざまな結果を返すメソッドのルート型が返されることがあります。

  • 階層内の任意の型に対して更新、挿入、または削除の各操作が定義されている場合、階層内のルート型に対して同じ操作を定義する必要があります。階層内の特定の型に対して操作を選択することはできません。

  • カスタム操作で、エンティティ引数のルート型または派生型を使用できます。インスタンスの実際の型がカスタム操作の型から派生する場合、その操作は許可されます。

  • DomainServiceDescription クラスは、指定された型および該当するすべてのクエリ メソッドに最適な更新、挿入、または削除メソッドを返します。

TypeDescriptionProvider

TypeDescriptionProvider (TDP) には、次の規則が適用されます。

  • 階層内のルート クラスがクエリ メソッドまたは IncludeAttribute 属性を介して公開される場合、LINQ to SQL の TypeDescriptionProvider と Entity Framework によって、エンティティの KnownTypeAttribute 属性宣言が自動的に推論されます。クエリ メソッドまたは IncludeAttribute 属性を介して派生型のみが公開される場合、既知の型は推論されません。

  • [新しいドメイン サービス クラスの追加] ダイアログ ボックスで、派生エンティティ型を選択できません。派生型のクエリ、挿入、更新、または削除メソッドを手動で作成する必要があります。

生成されたコード

継承階層内のエンティティに対してクライアント プロジェクトで生成されるコードには、次の規則が適用されます。

  • 継承階層ごとに EntitySet クラスが 1 つだけ生成されます。EntitySet の型パラメーターは、既知の継承階層内のルート型です。

  • 継承階層内の既知の型ごとに、対応するエンティティ型が生成されます。

  • 指定されている型に対してカスタム メソッドが生成され、任意の派生型で使用できます。

  • コンストラクターは、継承階層に従って連結されます。型ごとに OnCreated メソッドが呼び出されます。このメソッドはカスタマイズ可能です。

  • サーバー プロジェクトのエンティティ階層内のクラスが省略されている場合、そのクラスはクライアント プロジェクトの生成されるエンティティ階層内でも省略されます。省略されたクラスから派生する階層内の既知の型は、公開される適切な基本エンティティ型に親が再指定され、省略されたクラスの任意のパブリック プロパティが適切な派生型に生成されます。

  • 生成されたエンティティ クラスは、生成されたエンティティ クラス間の継承を許可する sealed ではありません。生成されたエンティティ クラスから派生するクラスを手動で作成することはできません。

実行時の動作

実行時のエンティティには、次の規則が適用されます。

  • LINQ クエリ変換でサポートされているクエリ演算子とフレームワーク メソッドのセットは、継承によって変更されません。

  • 既知の型のインスタンスは、クエリ操作と送信操作用の特定の型に従ってシリアル化および逆シリアル化されます。クエリ操作の場合、ポリモーフィックな EntitySet で計算されます。

  • インスタンスのキーと型は、1 つの SubmitChanges 操作のスコープ内では変更できません。たとえば、CustomerPrivateSectorCustomer に変換することはできません。型を変換するには、1 つの SubmitChanges 操作のインスタンスを削除し、別の SubmitChanges 操作の新しいインスタンスを作成します。