数据协定版本控制

随着应用程序的发展,可能还需要更改服务使用的数据协定。 本主题介绍如何对数据协定进行版本控制。 本主题介绍数据协定版本控制机制。 有关完整概述和规范性版本控制指南,请参阅 最佳做法:数据协定版本控制

破坏性变更与非破坏性变更

对数据协定的更改可能是重大更改,也可能是非重大更改。 当数据协定以非中断性方式更改时,使用较旧版本的协定的应用程序可以使用较新版本与应用程序通信,而使用较新版本的协定的应用程序可以使用旧版本与应用程序通信。 另一方面,破坏性变更会阻止一个或两个方向上的通信。

对类型的任何更改,只要不影响其传输方式和接收方式,都是非重大更改。 此类更改不会更改数据协定,仅更改基础类型。 例如,如果将字段的属性Name设置为DataMemberAttribute旧版本名称,则可以以非中断方式更改字段的名称。 以下代码显示了数据协定的版本 1。

// Version 1
[DataContract]
public class Person
{
    [DataMember]
    private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
    <DataMember()> _
    Private Phone As String
End Class

以下代码展示了一个非破坏性变化。

// Version 2. This is a non-breaking change because the data contract
// has not changed, even though the type has.
[DataContract]
public class Person
{
    [DataMember(Name = "Phone")]
    private string Telephone;
}
' Version 2. This is a non-breaking change because the data contract 
' has not changed, even though the type has.
<DataContract()> _
Public Class Person
    <DataMember(Name:="Phone")> _
    Private Telephone As String
End Class

有些更改确实修改了传输的数据,但这些更改可能是重大更改,也可能不是重大更改。 下面的更改始终是重大更改:

  • 更改数据协定中的 NameNamespace 值。

  • 使用Order属性更改DataMemberAttribute的数据成员顺序。

  • 重命名数据成员。

  • 更改数据成员的数据协定。 例如,将数据成员的类型从整数更改为字符串,或者从具有名为“Customer”的数据协定的类型更改为具有名为“Person”的数据协定的类型。

还可以进行以下更改。

添加和删除数据成员

在大多数情况下,添加或删除数据成员不会导致破坏性更改,除非要求严格的架构有效性(即用新实例验证旧架构)。

当具有额外字段的类型反序列化为缺少字段的类型时,将忽略额外信息。 (也可能存储用于往返目的;有关详细信息,请参阅 Forward-Compatible 数据协定)。

如果将缺少字段的类型反序列化为具有额外字段的类型,则额外字段将保留其默认值(通常为零或 null)。 (可能会更改默认值;有关详细信息,请参阅 Version-Tolerant 序列化回调。)

例如,可以在客户端上使用 CarV1 类,在服务上使用 CarV2 类;或者,可以在服务上使用 CarV1 类,在客户端上使用 CarV2 类。

// Version 1 of a data contract, on machine V1.
[DataContract(Name = "Car")]
public class CarV1
{
    [DataMember]
    private string Model;
}

// Version 2 of the same data contract, on machine V2.
[DataContract(Name = "Car")]
public class CarV2
{
    [DataMember]
    private string Model;

    [DataMember]
    private int HorsePower;
}
' Version 1 of a data contract, on machine V1.
<DataContract(Name:="Car")> _
Public Class CarV1
    <DataMember()> _
    Private Model As String
End Class

' Version 2 of the same data contract, on machine V2.
<DataContract(Name:="Car")> _
Public Class CarV2
    <DataMember()> _
    Private Model As String

    <DataMember()> _
    Private HorsePower As Integer
End Class

版本 2 终结点可以成功将数据发送到版本 1 终结点。 序列化数据协定版本 2 Car 会生成如下所示的 XML。

<Car>  
    <Model>Porsche</Model>  
    <HorsePower>300</HorsePower>  
</Car>  

V1 上的反序列化引擎找不到字段的匹配数据成员 HorsePower ,并丢弃该数据。

此外,版本 1 终结点还可以将数据发送到版本 2 终结点。 序列化数据协定版本 1 Car 会生成如下所示的 XML。

<Car>  
    <Model>Porsche</Model>  
</Car>  

版本 2 反序列化程序不知道将 HorsePower 字段设置为何值,因为传入的 XML 中没有匹配数据。 而是将字段设置为默认值 0。

必需的数据成员

通过将 IsRequiredDataMemberAttribute 属性设置为 true,可以将数据成员标记为必需的数据成员。 如果在反序列化时缺少所需的数据,则会引发异常,而不是将数据成员设置为其默认值。

添加必需的数据成员是重大更改。 也就是说,新类型仍然可以发送到具有旧类型的终结点,但无法反向发送。 移除在任何早期版本中标记为必需成员的数据成员也是重大更改。

IsRequired属性值从true更改为false并不导致中断,但如果任何先前版本的类型中没有该数据成员,将其从false更改为true可能会导致中断。

注释

虽然IsRequired属性设置为true,传入的数据可能为null或零,因此必须准备一个类型来处理这一可能性。 请勿使用IsRequired作为安全机制来防护有害的传入数据。

省略的默认值

可以将 DataMemberAttribute 属性上的属性设置为EmitDefaultValuefalse(尽管不建议这样做),如数据成员默认值中所述。 如果该属性设置为 false,而数据成员设置为其默认值(通常为 null 或零),则不会发出该数据成员。 这两种方式与不同版本中所需的数据成员不兼容:

  • 一个版本中具有必需数据成员的数据协定无法从该数据成员的 EmitDefaultValue 已设置为 false 的另一个版本接收默认值(null 或零)数据。

  • EmitDefaultValue 设置为 false 的必需数据成员不能用于序列化其默认值(null 或零),但可以在反序列化时接收此类值。 这会产生往返问题(可以读取数据,但无法写出相同的数据)。 因此,如果在一个版本中,IsRequiredtrueEmitDefaultValuefalse,则同样的组合应当应用到所有其他版本,任何数据协定版本都无法生成一个不会导致往返过程的值。

架构注意事项

有关为数据协定类型生成的架构的说明,请参阅 数据协定架构参考

WCF 为数据契约类型生成的模式未为版本化提供任何规定。 也就是说,从某个类型的特定版本导出的架构仅包含该版本中存在的那些数据成员。 实现 IExtensibleDataObject 接口不会更改类型的架构。

默认情况下,数据成员作为可选元素导出到架构。 也就是说, minOccurs (XML 属性)值设置为 0。 如果 minOccurs 设置为 1,则导出必需的数据成员。

如果要求严格遵循架构,则许多被视为无破坏性的更改实际上会导致破坏。 在前面的示例中,CarV1实例仅包含Model元素将针对CarV2模式进行验证(CarV2模式包含Horsepower和,但两者都是可选的)。 但是,相反情况并非如此:CarV2 实例无法通过 CarV1 架构的验证。

往返还需考虑一些额外因素。 有关详细信息,请参阅 Forward-Compatible 数据协定中的“架构注意事项”部分。

其他允许的更改

实现 IExtensibleDataObject 接口是非重大更改。 但是,在实现 IExtensibleDataObject 的版本之前,类型版本不提供往返过程支持。 有关详细信息,请参阅 Forward-Compatible 数据协定

枚举

添加或删除枚举成员是一项重大更改。 更改枚举成员的名称是重大更改,除非使用 EnumMemberAttribute 属性将其协定名称保持为与旧版本中的名称相同。 有关详细信息,请参阅 数据协定中的枚举类型

收集

大多数集合更改都是非中断性的,因为大多数集合类型在数据协定模型中相互交换。 但是,将一个非自定义集合转为自定义集合或反之则会造成破坏性更改。 此外,更改集合的自定义设置是一项重大更改;也就是说,更改其数据协定名称和命名空间、重复元素名称、键元素名称和值元素名称。 有关集合自定义的详细信息,请参阅 数据协定中的收集类型
当然,更改集合内容的数据协定(例如,从整数列表更改为字符串列表)是一项重大更改。

另请参阅