数据模型中的继承

利用 WCF RIA Services,您可以处理作为继承层次结构的一部分的实体。继承模型包括从另一个数据类派生的数据类。例如,多态继承模型可包含一个 Customer 实体和其他两个派生自 Customer 的实体(PublicSectorCustomerPrivateSectorCustomer)。利用 RIA Services ,您可以在域服务中编写查询方法,以便返回根类型和派生自根类型的其他类型的集合。或者,您可以编写仅返回派生类型的集合的查询方法。您也可以编写对根类型或任何派生的类型执行的数据修改方法。

数据模型

在服务器项目中,像定义任何数据类那样定义继承模型的数据类。您使用的对象模型可以是从数据访问层自动生成的类或手动创建的数据类。

您不需要通过域服务公开整个层次结构。相反,由域服务公开的层次结构中派生程度最低的类将被视为来自客户端的交互的根类型。从根类型派生的类型也可以公开到客户端。对于根类,您必须将要公开的任何派生类型包含在 KnownTypeAttribute 特性中。您可以通过不在 KnownTypeAttribute 特性中包含派生类型来省略它们,但随后必须确保不会从查询返回这些省略的类型的任何实例。下面的示例演示手动创建的数据模型,它包括基类 Customer 和两个派生类 PrivateSectorCustomerPublicSectorCustomerCustomer 将为数据操作的根类型。因此,两个派生的类都包括在 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 特性以定义两个数据类之间的关联。在数据模型示例中,CustomerOrder 之间定义了一个关联。当关联应用于根类型时,所有派生类型也会包含该关联。

可以将其他关联应用到根类型上不可用的派生类型。

使用继承的一般规则

以下规则适用于对 RIA Services 使用继承。

  • 仅支持对实体类型使用继承。非实体类型将被视为域服务操作签名中指定的类型。

  • 域服务操作中的返回值或参数不支持接口类型。

  • 生成代码时必须知道继承层次结构中的类型集。有关返回在生成代码时未指定的类型的行为是不确定的,将取决于实现。

  • 允许对实体类型的公共属性和字段使用 virtual 修饰符,但在客户端上生成相应的实体类型时会忽略此修饰符。

  • 不允许对域服务操作使用方法重载。

  • 不允许对实体类型的公共属性使用 new(C#)和 Shadows (Visual Basic) 关键字,否则在生成客户端代码时将导致错误。

  • 不能转换与继承相关的 LINQ 查询功能来执行域服务方法。具体而言,不支持 OfType<T>isasGetType() 运算符和方法。但是,这些运算符可直接对 LINQ to Objects 查询中的 EntitySetEntityCollection 使用。

实体继承层次结构

以下规则适用于定义继承层次结构。

  • 可以使用 System.Runtime.Serialization.KnownTypeAttribute 特性来指定通过域服务公开的所有已知的派生实体类型。

  • 必须在通过域服务公开的层次结构中的根类型上指定该层次结构中的已知类型。

  • 已知类型集中的每个类都必须是 public

  • 层次结构中的一个或多个类可以是 abstract

  • 声明已知类型时可以省略层次结构中的一个或多个类。派生自省略的类的类在继承层次结构中将会扁平化,并基于已知类型声明中的下一个更高级别父类重新设置父级。省略的类中的属性将自动在派生自这些类的任何公开的类型上生成。

  • 根类必须具有一个或多个标记有 KeyAttribute 特性的属性。可以将该特性应用于在根类型的未公开基类型中定义的属性。层次结构中省略的实体类上的公共实体属性将自动在派生自该实体类的公开实体类型上生成。

  • 声明以及关联的使用没有更改。

DomainService 操作

以下规则适用于对继承层次结构中的实体定义域服务操作。

  • 必须至少有一个与层次结构中的根类型对应的查询方法。其他查询操作可对返回值使用派生程度更大的类型。

  • 查询操作可为返回多态结果的方法返回根类型。

  • 如果为层次结构中的任何类型定义了更新、插入或删除操作,则还必须为层次结构中的根类型定义相同操作。不能只为层次结构中的某些类型特意地选择某个操作。

  • 自定义操作可以对实体参数使用根类型或派生类型。如果实体的实际类型派生自自定义操作中的类型,则允许该操作。

  • DomainServiceDescription 类将为给定类型和所有适用的查询方法返回最合适的更新、插入或删除方法方法。

TypeDescriptionProvider

以下规则也适用于 TypeDescriptionProvider (TDP)。

  • 当层次结构中的根类通过查询方法或 IncludeAttribute 特性公开时,LINQ to SQL 和实体框架的 TypeDescriptionProvider 将自动为实体推断 KnownTypeAttribute 特性声明。只有当派生类型通过查询方法或 IncludeAttribute 特性公开时,才不会推断已知类型。

  • **“添加新的域服务类”**对话框不允许选择派生实体类型。您必须为派生类型手动创建查询、插入、更新或删除方法。

生成的代码

以下规则适用于在客户端项目中为继承层次结构中的实体生成的代码。

  • 将为每个继承层次结构生成一个 EntitySet 类。EntitySet 的类型参数是已知继承层次结构中的根类型。

  • 对于继承层次结构中的每个已知类型,将会生成一个对应的实体类型。

  • 将指定的类型上生成自定义方法,并可供任何派生类型使用。

  • 构造函数将根据继承层次结构链接在一起。将为每个类型调用 OnCreated 方法,并且可以对此方法进行自定义。

  • 如果某个类在服务器项目中的实体层次结构中被省略,则它在客户端项目中生成的实体层次结构中也会被省略。派生自某个省略的类的层次结构中的已知类型的父级将会重新设置为相应的公开基实体类型,省略的类中的任何公开属性将在相应的派生类型中生成。

  • 生成的实体类不是 sealed 以允许生成的实体类之间的继承。不支持手动创建派生自生成的实体类的类。

运行时行为

以下规则适用运行时的实体。

  • 继承不会更改 LINQ 查询转换支持的查询运算符和框架方法集。

  • 已知类型的实例将根据其查询和提交操作的特定类型进行序列化和反序列化。查询操作将在多态 EntitySet 中累计。

  • 实例的键和类型不能在单个 SubmitChanges 操作的范围内更改。例如,Customer 对象不能转换为 PrivateSectorCustomer 对象。您可以通过在一个 SubmitChanges 操作中删除实例并在另一个 SubmitChanges 操作中创建新实例来转换类型。