最佳做法:数据协定版本控制

本主题列出了创建可随时间推移轻松发展的数据协定的最佳做法。 有关数据协定的详细信息,请参阅 “使用数据协定”中的主题。

有关架构验证的说明

在讨论数据协定版本控制时,请务必注意,由 Windows Communication Foundation (WCF) 导出的数据协定架构没有任何版本控制支持,除了元素默认标记为可选的事实之外。

这意味着,即使是最常见的版本控制方案(如添加新数据成员)也不能以与给定架构无缝的方式实现。 较新版本的数据协定(例如,新数据成员)不使用旧架构进行验证。

但是,在很多情况下,不需要严格的架构符合性。 许多 Web 服务平台(包括使用 ASP.NET 创建的 WCF 和 XML Web 服务)默认不执行架构验证,因此可以容忍架构未描述的额外元素。 使用此类平台时,许多版本控制方案更易于实现。

因此,存在两套数据协定版本管理准则:一套用于严格架构有效性十分重要的方案;另一套用于严格架构有效性不太重要的方案。

需要架构验证时的版本控制

如果在所有方向(新到旧和旧到新)都需要严格的架构有效性,那么数据协定应被视为不可变。 如果需要版本控制,应创建具有不同名称或命名空间的新数据协定,并且应相应地对使用数据类型的服务协定进行版本控制。

例如,假设一个名为 PoProcessing 的采购订单处理服务协定具有 PostPurchaseOrder 操作,并采用一个符合 PurchaseOrder 数据协定的参数。 如果PurchaseOrder合同必须更改,则必须创建一个新的数据合同,即PurchaseOrder2 ,并包含更改。 然后,必须在服务协定级别处理版本控制。 例如,通过创建一个采用PostPurchaseOrder2参数的PurchaseOrder2操作,或通过创建一个PoProcessing2服务合约,其中PostPurchaseOrder操作采用PurchaseOrder2数据合约。

请注意,被其他数据合同引用的数据合同的更改也会影响到服务模型层。 例如,在前面的方案中,数据协定PurchaseOrder不需要更改。 但是,它包含 Customer 数据协定的数据成员,而该数据协定又包含 Address 数据协定的数据成员,而后者确实需要更改。 在这种情况下,需要创建 Address2 包含所需更改的数据协定、 Customer2 包含 Address2 数据成员的数据协定以及 PurchaseOrder2 包含 Customer2 数据成员的数据协定。 与之前的情况一样,服务合同也将进行版本化。

尽管在这些示例中更改了名称(通过追加“2”),但建议通过追加版本号或日期来更改命名空间而不是名称。 例如, http://schemas.contoso.com/2005/05/21/PurchaseOrder 数据协定将更改为 http://schemas.contoso.com/2005/10/14/PurchaseOrder 数据协定。

有关详细信息,请参阅最佳做法: 服务版本控制

有时,必须保证应用程序发送的消息的严格架构符合性,但不能依赖传入消息严格符合架构。 在这种情况下,传入的消息可能包含多余的数据。 WCF 存储并返回多余的值,这会导致发送的消息不符合架构。 若要避免此问题,应关闭往返功能。 可通过两种方式来执行此操作。

有关往返的详细信息,请参阅向前兼容的数据协定

不需要架构验证时的版本控制

很少需要严格的架构符合性。 许多平台可容忍架构未描述的额外元素。 只要可以容忍,就可以使用 数据协定版本控制Forward-Compatible 数据协定 中所述的完整功能集。 建议遵循以下准则。

必须严格遵循某些准则,以便在需要旧版本的地方发送新版本,或者在需要新版本的地方发送旧版本。 其他准则并非严格要求,但在此处列出,因为它们可能会受到架构版本控制的未来的影响。

  1. 不要尝试按类型继承对数据协定进行版本控制。 若要创建更高版本,请更改现有类型的数据协定或创建新的不相关类型。

  2. 允许将继承与数据协定一起使用,前提是继承不用作版本控制机制,并且遵循某些规则。 如果某个类型派生自特定基类型,请不要在将来的版本中派生自其他基类型(除非它具有相同的数据协定)。 有一个例外:可以在数据协定类型与其基类型之间将类型插入层次结构中,但前提是它不包含与层次结构中其他类型的任何可能版本中具有相同名称的数据成员。 通常,在相同继承层次结构的不同级别使用相同的名称的数据成员可能会导致严重的版本控制问题,应避免此问题。

  3. 从数据协定的第一个版本开始,始终实现 IExtensibleDataObject 可启用往返。 有关详细信息,请参阅 Forward-Compatible 数据协定。 如果在未实现此接口的情况下发布了一个或多个类型的版本,请在该类型的下一个版本中实现它。

  4. 在更高版本中,请勿更改数据协定名称或命名空间。 如果更改数据协定基础类型的名称或命名空间,请务必使用相应的机制(例如NameDataContractAttribute该属性)来保留数据协定名称和命名空间。 有关命名的详细信息,请参阅 数据协定名称

  5. 在更高版本中,请勿更改任何数据成员的名称。 如果更改数据成员基础的字段、属性或事件的名称,请使用 Name 该属性 DataMemberAttribute 来保留现有数据成员名称。

  6. 在更高版本中,请勿更改数据成员底层的任何字段、属性或事件的类型,以免导致该数据成员的数据协定发生变化。 请记住,接口类型相当于用作确定预期数据契约的Object

  7. 在以后的版本中,不要通过调整 Order 属性 (Attribute) 的 DataMemberAttribute 属性 (Property) 来更改现有数据成员的顺序。

  8. 在更高版本中,可以添加新的数据成员。 它们应始终遵循以下规则:

    1. 属性 IsRequired 应始终保留在其默认值处 false

    2. 如果成员的 null 默认值或零是不能接受的,则应使用 OnDeserializingAttribute 回调方法提供合理的默认值,以防该成员不存在于传入流中。 有关回调的详细信息,请参阅 Version-Tolerant 序列化回调

    3. DataMemberAttribute.Order 属性应用于确保所有新添加的数据成员显示在现有数据成员之后。 建议执行此作的方法如下:第一个版本数据协定中的数据成员不应设置其 Order 属性。 数据协定版本 2 中添加的所有数据成员都应将其 Order 属性设置为 2。 将添加到数据协定版本 3 中的所有数据成员的 Order 设置为 3,依次类推。 允许将多个数据成员设置为同 Order 一个数字。

  9. 在以后的版本中,不要移除数据成员,即使在以前的版本中 IsRequired 属性保留为其默认属性 false

  10. 不要在不同版本之间更改任何现有数据成员的 IsRequired 属性。

  11. 对于必需的数据成员(其中IsRequiredtrue),请勿在不同版本之间更改EmitDefaultValue属性。

  12. 不要尝试创建分支版本控制层次结构。 即,从任意版本到任何其他版本的至少一个方向上,应始终有一个路径仅使用这些准则允许的更改。

    例如,如果 Person 数据协定的版本 1 仅包含姓名数据成员,那么您不应创建仅添加年龄成员的版本 2a,也不应创建仅添加地址成员的版本 2b。 从 2a 到 2b 将涉及删除年龄并添加地址。转换到另一个方向将需要删除地址并添加年龄。 这些准则不允许删除成员。

  13. 通常不应在应用程序的新版本中创建现有数据协定类型的新子类型。 同样,不应创建新的数据协定以代替声明为对象或接口类型的数据成员。 仅当知道可以将新类型添加到旧应用程序的所有实例的已知类型列表中时,才允许创建新类。 例如,在您的应用程序的版本 1 中,您可能会有 LibraryItem 数据合同类型,其中包含 Book 和 Newspaper 数据合同子类型。 然后,LibraryItem 将具有包含书籍和报纸的已知类型列表。 假设你现在在版本 2 中添加一个“杂志”类型,该类型是 LibraryItem 的子类型。 如果将杂志实例从版本 2 发送到版本 1,则“杂志”数据协定在已知类型列表中找不到,并且会引发异常。

  14. 不应在版本之间添加或删除枚举成员。 除非在 EnumMemberAttribute 属性上使用 Name 属性,以便在数据协定模型中保持名称不变,否则不应重命名枚举成员。

  15. 集合在数据协定模型中可互换,如 数据协定中的集合类型中所述。 这允许极大的灵活性。 但是,请确保不要无意中以不可互换的方式在不同版本之间更改集合的类型。 例如,不要从非自定义集合(即没有 CollectionDataContractAttribute 属性)更改为自定义集合或自定义集合到非自定义集合。 此外,请勿在不同版本之间更改 CollectionDataContractAttribute 的属性。 唯一允许的更改是在基础集合类型的名称或命名空间发生更改时添加 Name 或 Namespace 属性,并且需要使其数据协定名称和命名空间与在以前的版本中相同。

当特殊情况适用时,可以安全地忽略此处列出的一些准则。 在偏离准则之前,请确保完全了解所涉及的序列化、反序列化和架构机制。

另请参阅