데이터 모델의 상속
WCF RIA Services를 사용하여 상속 계층 구조의 일부인 엔터티로 작업할 수 있습니다. 상속 모델에는 다른 데이터 클래스에서 파생된 데이터 클래스가 포함되어 있습니다. 예를 들어 다형성 상속 모델에는 Customer
엔터티와 Customer
에서 파생되는 PublicSectorCustomer
및 PrivateSectorCustomer
엔터티가 포함될 수 있습니다. RIA Services 를 사용하면 루트 형식 및 루트 형식에서 파생된 다른 형식의 컬렉션을 반환하는 쿼리 메서드를 도메인 서비스에서 작성할 수 있습니다. 아니면 파생 형식의 컬렉션만 반환하는 쿼리 메서드를 작성할 수도 있습니다. 또한 루트 형식 또는 파생 형식에 대해 작동하는 데이터 수정 메서드를 작성할 수도 있습니다.
데이터 모델
서버 프로젝트에서 데이터 클래스를 정의하는 것과 같이 상속 모델의 데이터 클래스를 정의할 수 있습니다. 사용하는 개체 모델은 데이터 액세스 계층에서 자동으로 생성된 클래스이거나 수동으로 만들어진 데이터 클래스일 수 있습니다.
도메인 서비스를 통해 전체 계층 구조를 노출할 필요가 없습니다. 대신 도메인 서비스에서 노출되는 계층 구조에서 가장 적게 파생된 클래스는 클라이언트에서 상호 작용을 위한 루트 형식으로 간주됩니다. 루트 형식에서 파생된 형식도 클라이언트에 노출될 수 있습니다. 루트 클래스에서는 노출할 파생 형식을 KnownTypeAttribute 특성에 포함해야 합니다. 노출할 파생 형식을 KnownTypeAttribute 특성에 포함하지 않는 방법으로 파생 형식을 생략할 수 있지만 이 생략된 형식의 인스턴스를 쿼리에서 반환하지 않도록 해야 합니다. 다음 예제에서는 기본 클래스 Customer
와 두 파생 클래스 PrivateSectorCustomer
및 PublicSectorCustomer
를 포함하는 수동으로 만들어진 데이터 모델을 보여 줍니다. Customer
는 데이터 작업의 루트 형식이 됩니다. 따라서 두 파생 클래스가 Customer
클래스의 KnownTypeAttribute 특성에 포함되는데, 이는 이 특성의 GetType 매개 변수를 사용하여 지정된 것입니다.
<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
개체를 포함할 수 있습니다. 쿼리 메서드가 파생 형식만 반환하도록 지정할 수도 있습니다. 다음 예제에서는 이러한 세 형식을 각각 반환하는 쿼리 메서드를 보여 줍니다.
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 클래스에서 단일 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);
}
데이터 수정
상속 계층 구조의 개체를 업데이트, 삽입 및 삭제하기 위한 도메인 서비스 메서드도 추가할 수 있습니다. 쿼리 메서드의 경우와 마찬가지로 작업의 루트 형식 또는 파생 형식을 지정할 수 있습니다. 그러나 파생 형식에서 사용할 수 있는 모든 업데이트, 삽입 또는 삭제 작업은 루트 형식에서도 사용할 수 있도록 설정해야 합니다. 계층 구조의 형식에 대한 명명된 업데이트 메서드도 추가할 수 있습니다. 클라이언트에서 해당하는 명명된 업데이트 메서드가 메서드에 지정된 형식에 대해 생성됩니다.
클라이언트가 처리를 위해 서버에 변경 내용을 제출할 때마다 가장 많이 파생된 버전의 해당 삽입, 업데이트 또는 삭제 메서드가 개체별로 실행됩니다. 파생 형식 메서드에서 수행할 작업을 선택해야 합니다. 예를 들면 해당 루트 형식 메서드를 호출할지 여부 등을 선택합니다.
다음 예제에서는 두 업데이트 메서드와 하나의 명명된 업데이트 메서드에 대한 시그니처를 보여 줍니다. 값을 업데이트하는 논리를 구현하는 코드는 이 예제에 포함되어 있지 않습니다.
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 특성을 적용하여 두 데이터 클래스 간의 연결을 정의합니다. 데이터 모델 예제에서는 Customer
및 Order
간에 연결이 정의되어 있습니다. 연결이 루트 형식에 적용되면 모든 파생 형식도 그 연결을 포함합니다.
루트 형식에서 사용할 수 없는 추가 연결을 파생 형식에 적용할 수 있습니다.
상속 사용에 대한 일반 규칙
RIA Services에서 상속을 사용하는 데 적용되는 규칙은 다음과 같습니다.
상속은 엔터티 형식에만 지원됩니다. 비 엔터티 형식은 도메인 서비스 작업 시그니처에 지정된 형식으로 취급됩니다.
인터페이스 형식은 도메인 서비스 작업의 반환 값이나 매개 변수에 지원되지 않습니다.
상속 계층 구조의 형식 집합은 코드 생성 시 알려져야 합니다. 코드 생성 시 지정되지 않은 형식을 반환하는 동작은 지정되지 않으며 구현에 따라 결정됩니다.
엔터티 형식의 공용 속성 및 필드에 대한 virtual 한정자가 허용되지만 클라이언트에서 해당 엔터티 형식을 생성할 때 무시됩니다.
도메인 서비스 작업에 대한 메서드 오버로드가 허용되지 않습니다.
공용 속성에 대한 new(C#) 및 Shadows(Visual Basic) 키워드가 엔터티 형식에서 허용되지 않으며 클라이언트 코드를 생성할 때 오류를 발생시킵니다.
상속과 관련된 LINQ 쿼리 기능은 도메인 서비스 메서드의 실행을 위해 변환할 수 없습니다. 특히 OfType<T>, is, as 및 GetType() 연산자 및 메서드가 지원되지 않습니다. 그러나 LINQ to Objects 쿼리에서 EntitySet 또는 EntityCollection에 대해 이러한 연산자를 직접 사용할 수 있습니다.
엔터티 상속 계층 구조
상속 계층 구조를 정의하는 데 적용되는 규칙은 다음과 같습니다.
System.Runtime.Serialization.KnownTypeAttribute 특성을 사용하여 도메인 서비스를 통해 노출하는 모든 알려진 파생 엔터티 형식을 지정합니다.
계층 구조의 알려진 형식은 도메인 서비스를 통해 노출되는 계층 구조의 루트 형식에 대해 지정해야 합니다.
설정된 알려진 형식의 각 클래스는 public이어야 합니다.
계층 구조의 하나 이상의 클래스는 abstract일 수 있습니다.
알려진 형식을 선언할 때 계층 구조의 클래스를 하나 이상 생략할 수 있습니다. 생략된 클래스에서 파생된 클래스는 알려진 형식 선언에 있는 다음 상위 부모 클래스를 기반으로 상속 계층 구조에서 평면화되고 부모가 재지정됩니다. 생략된 클래스의 속성은 해당 클래스에서 파생된 모든 노출된 형식에 대해 자동으로 생성됩니다.
루트 클래스에는 KeyAttribute 특성으로 표시된 속성이 하나 이상 있어야 합니다. 해당 루트 형식의 노출되지 않은 기본 형식에 정의된 속성에 특성을 적용할 수 있습니다. 계층 구조에서 생략된 엔터티 클래스에 대한 공용 엔터티 속성은 해당 클래스에서 파생된 노출된 엔터티 형식에 대해 자동으로 생성됩니다.
선언과 연결의 사용은 변경되지 않습니다.
DomainService 작업
상속 계층 구조의 엔터티에 대한 도메인 서비스 작업을 정의하는 데 적용되는 규칙은 다음과 같습니다.
계층 구조의 루트 형식에 해당하는 쿼리 메서드가 적어도 하나 있어야 합니다. 추가 쿼리 작업에서는 더 많이 파생된 형식을 반환 값에 사용할 수 있습니다.
쿼리 작업은 다형적 결과를 반환하는 메서드에 대해 루트 형식을 반환할 수 있습니다.
업데이트, 삽입 또는 삭제 작업이 계층 구조의 형식에 대해 정의되어 있으면 계층 구조의 루트 형식에 대해 동일한 작업을 정의해야 합니다. 계층 구조의 특정 형식에 대해서만 작업을 선택할 수는 없습니다.
사용자 지정 작업에서는 루트 형식 또는 파생 형식을 엔터티 인수에 사용할 수 있습니다. 인스턴스의 실제 형식이 사용자 지정 작업의 형식에서 파생되면 작업이 허용됩니다.
DomainServiceDescription 클래스는 지정된 형식에 가장 적합한 업데이트, 삽입 또는 삭제 메서드와 모든 적용 가능한 쿼리 메서드를 반환합니다.
TypeDescriptionProvider
TypeDescriptionProvider(TDP)에 적용되는 규칙은 다음과 같습니다.
계층 구조의 루트 클래스가 쿼리 메서드 또는 IncludeAttribute 특성을 통해 노출되면 LINQ to SQL 및 Entity Framework에 대한 TypeDescriptionProvider가 엔터티에 대한 KnownTypeAttribute 특성 선언을 자동으로 유추합니다. 파생 형식만 쿼리 메서드 또는 IncludeAttribute 특성을 통해 노출되는 경우에는 알려진 형식이 유추되지 않습니다.
새 도메인 서비스 클래스 추가 대화 상자에서는 파생 엔터티 형식을 선택할 수 없습니다. 파생 형식에 대한 쿼리, 삽입, 업데이트 또는 삭제 메서드를 수동으로 만들어야 합니다.
생성된 코드
상속 계층 구조의 엔터티에 대해 클라이언트 프로젝트에서 생성된 코드에 적용되는 규칙은 다음과 같습니다.
상속 계층 구조마다 EntitySet 클래스가 하나만 생성됩니다. EntitySet의 형식 매개 변수는 알려진 상속 계층 구조의 루트 형식입니다.
상속 계층 구조의 알려진 각 형식에 해당하는 엔터티 형식이 생성됩니다.
사용자 지정 메서드가 해당 메서드가 지정된 형식에 대해 생성되며 모든 파생 형식에서 사용할 수 있습니다.
생성자는 상속 계층 구조에 따라 연결됩니다. OnCreated 메서드는 각 형식에 대해 호출되며 사용자 지정할 수 있습니다.
서버 프로젝트의 엔터티 계층 구조에서 클래스가 생략되면 클라이언트 프로젝트에서 생성된 엔터티 계층 구조에서도 생략됩니다. 생략된 클래스에서 파생된 계층 구조의 알려진 형식은 적절한 노출된 기본 엔터티 형식으로 부모가 재지정되며 생략된 클래스의 모든 공용 속성은 적절한 파생 형식에서 생성됩니다.
생성된 엔터티 클래스는 sealed 클래스가 아니므로 생성된 엔터티 클래스 간의 상속이 허용됩니다. 생성된 엔터티 클래스에서 파생된 클래스를 수동으로 만들 수 없습니다.
런타임 동작
런타임에서 엔터티에 적용되는 규칙은 다음과 같습니다.
상속으로 인해 LINQ 쿼리 변환에 대해 지원되는 쿼리 연산자 및 프레임워크 메서드의 집합이 변경되지 않습니다.
알려진 형식의 인스턴스가 쿼리 및 제출 작업의 특정 형식에 따라 serialize 및 deserialize됩니다. 쿼리 작업의 경우 이러한 인스턴스는 다형성 EntitySet에 누적됩니다.
인스턴스의 키 및 형식은 단일 SubmitChanges 작업의 범위 내에서 변경될 수 없습니다. 예를 들어
Customer
는PrivateSectorCustomer
로 변환될 수 없습니다. 한 SubmitChanges 작업에서 인스턴스를 삭제하고 다른 SubmitChanges 작업에서 새 인스턴스를 만들어 형식을 변환할 수 있습니다.