データ コントラクトのバージョン管理Data Contract Versioning

アプリケーションの進化に伴って、サービスが使用するデータ コントラクトを変更することが必要になる場合があります。As applications evolve, you may also have to change the data contracts the services use. ここでは、データ コントラクトをバージョン管理する方法について説明します。This topic explains how to version data contracts. データ コントラクトのバージョン管理のメカニズムについても説明します。This topic describes the data contract versioning mechanisms. 完全な概要と規範となるバージョン管理のガイダンスについては、「 ベストプラクティス: データコントラクトのバージョン管理」を参照してください。For a complete overview and prescriptive versioning guidance, see Best Practices: Data Contract Versioning.

互換性に影響する変更と影響しない変更Breaking vs. Nonbreaking Changes

データ コントラクトへの変更には、互換性に影響するものと影響しないものとがあります。Changes to a data contract can be breaking or nonbreaking. 互換性に影響しない方法でデータ コントラクトを変更すると、古いバージョンのコントラクトを使用するアプリケーションは新しいバージョンを使用するアプリケーションと通信できます。また、新しいバージョンを使用するアプリケーションも古いバージョンを使用するアプリケーションと通信できます。When a data contract is changed in a nonbreaking way, an application using the older version of the contract can communicate with an application using the newer version, and an application using the newer version of the contract can communicate with an application using the older version. 一方、互換性に影響する変更では、一方向または双方向で通信できなくなります。On the other hand, a breaking change prevents communication in one or both directions.

型を変更してもその送受信方法に影響しない場合は、互換性に影響しない変更と呼びます。Any changes to a type that do not affect how it is transmitted and received are nonbreaking. このような変更では、データ コントラクトは変更されず、基になる型のみが変更されます。Such changes do not change the data contract, only the underlying type. たとえば、フィールドの名前を変更した後で NameDataMemberAttribute プロパティを以前のバージョン名に設定すれば、互換性に影響しません。For example, you can change the name of a field in a nonbreaking way if you then set the Name property of the DataMemberAttribute to the older version name. 次のコードに、バージョン 1 のデータ コントラクトを示します。The following code shows version 1 of a data contract.

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

互換性に影響しない変更を次のコードに示します。The following code shows a nonbreaking change.

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

変更によっては、送信されるデータを変更しても、必ずしも互換性に影響しない場合もあります。Some changes do modify the transmitted data, but may or may not be breaking. 次の変更は常に互換性に影響します。The following changes are always breaking:

  • データ コントラクトの Name 値または Namespace 値の変更。Changing the Name or Namespace value of a data contract.

  • OrderDataMemberAttribute プロパティを使用したデータ メンバーの順序の変更。Changing the order of data members by using the Order property of the DataMemberAttribute.

  • データ メンバーの名前の変更。Renaming a data member.

  • データ メンバーのデータ コントラクトの変更 Changing the data contract of a data member. (たとえば、データ メンバーの型を整数型から文字列型に変更したり、"Customer" という名前のデータ コントラクトを持つ型を "Person" という名前のデータ コントラクトを持つ型に変更したりするなど)。For example, changing the type of data member from an integer to a string, or from a type with a data contract named "Customer" to a type with a data contract named "Person".

以下のような変更も考えられます。The following changes are also possible.

データ メンバーの追加と削除Adding and Removing Data Members

厳密なスキーマ検証 (古いスキーマに基づく新しいインスタンスの検証) が必要ではない限り、多くの場合、データ メンバーの追加または削除は互換性に影響する変更ではありません。In most cases, adding or removing a data member is not a breaking change, unless you require strict schema validity (new instances validating against the old schema).

追加のフィールドを持つ型が、そのフィールドを持たない型に逆シリアル化された場合、追加のフィールドにある情報は無視されます When a type with an extra field is deserialized into a type with a missing field, the extra information is ignored. (ラウンドトリップのために保存される場合もあります。詳細については、「 上位互換性のあるデータコントラクト」を参照してください)。(It may also be stored for round-tripping purposes; for more information, see Forward-Compatible Data Contracts).

一部のフィールドが足りない型が、追加のフィールドを持つ型に逆シリアル化された場合、追加のフィールドは既定値 (通常はゼロまたは null) のままになります When a type with a missing field is deserialized into a type with an extra field, the extra field is left at its default value, usually zero or null. (既定値は変更される可能性があります。詳細については、「 バージョントレラントなシリアル化コールバック」を参照してください)。(The default value may be changed; for more information, see Version-Tolerant Serialization Callbacks.)

たとえば、クライアントで CarV1 クラスを使用し、サービスで CarV2 クラスを使用することも、サービスで CarV1 クラスを使用し、クライアントで CarV2 クラスを使用することもできます。For example, you can use the CarV1 class on a client and the CarV2 class on a service, or you can use the CarV1 class on a service and the CarV2 class on a client.

// 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 のエンドポイントに正常にデータを送信できます。The version 2 endpoint can successfully send data to the version 1 endpoint. Car データ コントラクトのバージョン 2 をシリアル化すると、次のような XML が生成されます。Serializing version 2 of the Car data contract yields XML similar to the following.

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

バージョン 1 の逆シリアル化エンジンでは、HorsePower フィールドに一致するデータ メンバーを見つけられないため、データは破棄されます。The deserialization engine on V1 does not find a matching data member for the HorsePower field, and discards that data.

また、バージョン 1 のエンドポイントからバージョン 2 のエンドポイントにデータを送信することもできます。Also, the version 1 endpoint can send data to the version 2 endpoint. Car データ コントラクトのバージョン 1 をシリアル化すると、次のような XML が生成されます。Serializing version 1 of the Car data contract yields XML similar to the following.

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

受信 XML に一致するデータがないため、バージョン 2 のデシリアライザーでは HorsePower フィールドを何に設定するのか判別できません。The version 2 deserializer does not know what to set the HorsePower field to, because there is no matching data in the incoming XML. そのため、このフィールドには既定値である 0 が設定されます。Instead, the field is set to the default value of 0.

必須データ メンバーRequired Data Members

IsRequiredDataMemberAttribute プロパティを true に設定すると、データ メンバーを必須のデータ メンバーとしてマークできます。A data member may be marked as being required by setting the IsRequired property of the DataMemberAttribute to true. 逆シリアル化中に必須データがない場合、データ メンバーは既定値に設定されず、代わりに例外がスローされます。If required data is missing while deserializing, an exception is thrown instead of setting the data member to its default value.

必須のデータ メンバーの追加は、互換性に影響する変更です。Adding a required data member is a breaking change. つまり、新しい型を古い型のエンドポイントに送信することはできますが、その逆はできなくなります。That is, the newer type can still be sent to endpoints with the older type, but not the other way around. 以前のバージョンで必須としてマークされていたデータ メンバーを削除することも、互換性に影響する変更です。Removing a data member that was marked as required in any prior version is also a breaking change.

IsRequired プロパティの値を true から false に変更することは互換性に影響しませんが、false から true に変更すると、これよりも以前のいずれかのバージョンの型にこのデータ メンバーが存在していない場合、互換性に影響することがあります。Changing the IsRequired property value from true to false is not breaking, but changing it from false to true may be breaking if any prior versions of the type do not have the data member in question.

注意

IsRequired プロパティが true に設定されていても、受信データは NULL またはゼロの場合があるので、型ではこの可能性を処理できるようにしておく必要があります。Although the IsRequired property is set to true, the incoming data may be null or zero, and a type must be prepared to handle this possibility. 不適切な受信データに対するセキュリティ保護メカニズムとして IsRequired を使用しないでください。Do not use IsRequired as a security mechanism to protect against bad incoming data.

既定値の省略Omitted Default Values

EmitDefaultValue false データメンバーの既定値」で説明されているように、DataMemberAttribute 属性のプロパティをに設定することもできます (推奨されません)。It is possible (although not recommended) to set the EmitDefaultValue property on the DataMemberAttribute attribute to false, as described in Data Member Default Values. このプロパティが false に設定されている場合、データ メンバーが既定値 (通常 NULL またはゼロ) に設定されていても、そのデータ メンバーは出力されません。If this setting is false, the data member will not be emitted if it is set to its default value (usually null or zero). これにより、次の 2 つの点で、異なるバージョンの必須データ メンバーの互換性が失われます。This is not compatible with required data members in different versions in two ways:

  • あるバージョンで必須になっているデータ メンバーを持つデータ コントラクトは、データ メンバーの EmitDefaultValuefalse に設定されている別のバージョンから既定 (NULL またはゼロ) のデータを受信できません。A data contract with a data member that is required in one version cannot receive default (null or zero) data from a different version in which the data member has EmitDefaultValue set to false.

  • EmitDefaultValuefalse に設定されている必須データ メンバーは、既定値 (NULL またはゼロ) をシリアル化で使用できませんが、逆シリアル化ではこのような値を受信できます。A required data member that has EmitDefaultValue set to false cannot be used to serialize its default (null or zero) value, but can receive such a value on deserialization. これによりラウンド トリップの問題が発生します (データを読み取ることはできますが、書き込むことができません)。This creates a round-tripping problem (data can be read in but the same data cannot then be written out). そのため、あるバージョンにおいて IsRequiredtrue であり、EmitDefaultValuefalse である場合、どのバージョンのデータ コントラクトにおいてもラウンド トリップを発生させる値を生成できるように、これ以外のすべてのバージョンに同じ組み合わせを適用する必要があります。Therefore, if IsRequired is true and EmitDefaultValue is false in one version, the same combination should apply to all other versions such that no version of the data contract would be able to produce a value that does not result in a round trip.

スキーマの考慮事項Schema Considerations

データコントラクト型に対して生成されるスキーマの説明については、「 データコントラクトスキーマの参照」を参照してください。For an explanation of what schema is produced for data contract types, see Data Contract Schema Reference.

データコントラクト型に対して WCF によって生成されるスキーマは、バージョン管理のための準備を行いません。The schema WCF produces for data contract types makes no provisions for versioning. つまり、あるバージョンの型からエクスポートされたスキーマには、そのバージョンに存在するデータ メンバーのみが含まれます。That is, the schema exported from a certain version of a type contains only those data members present in that version. IExtensibleDataObject インターフェイスを実装しても、型のスキーマは変更されません。Implementing the IExtensibleDataObject interface does not change the schema for a type.

データ メンバーは、既定でオプション要素としてスキーマにエクスポートされます。Data members are exported to the schema as optional elements by default. つまり、minOccurs (XML 属性) の値は 0 に設定されています。That is, the minOccurs (XML attribute) value is set to 0. minOccurs を 1 に設定した場合、必須データ メンバーがエクスポートされます。Required data members are exported with minOccurs set to 1.

スキーマを厳密に遵守する必要がある場合、互換性に影響しないと思われる変更の多くが、実際には互換性に影響します。Many of the changes considered to be nonbreaking are actually breaking if strict adherence to the schema is required. 前述の例では、CarV1 要素だけを持つ Model インスタンスは、CarV2 スキーマ (ModelHorsepower の 2 つの要素を持ちますが、いずれも省略可能です) に対して有効です。In the preceding example, a CarV1 instance with just the Model element would validate against the CarV2 schema (which has both Model and Horsepower, but both are optional). しかし、この逆は真ではありません。つまり、CarV2 インスタンスは CarV1 スキーマに対して検証が失敗します。However, the reverse is not true: a CarV2 instance would fail validation against the CarV1 schema.

ラウンド トリップにもいくつかの考慮事項があります。Round-tripping also entails some additional considerations. 詳細については、「 上位互換性のあるデータコントラクト」の「スキーマの考慮事項」セクションを参照してください。For more information, see the "Schema Considerations" section in Forward-Compatible Data Contracts.

許可されるその他の変更Other Permitted Changes

IExtensibleDataObject インターフェイスの実装は、互換性に影響しない変更です。Implementing the IExtensibleDataObject interface is a nonbreaking change. ただし、IExtensibleDataObject が実装されていた型のバージョンより前のバージョンでは、ラウンド トリップはサポートされていません。However, round-tripping support does not exist for versions of the type prior to the version in which IExtensibleDataObject was implemented. 詳細については、「上位互換性のあるデータ コントラクト」を参照してください。For more information, see Forward-Compatible Data Contracts.

列挙型Enumerations

列挙体メンバーの追加や削除は、互換性に影響する変更です。Adding or removing an enumeration member is a breaking change. EnumMemberAttribute 属性を使用して古いバージョンのコントラクト名を保持しない限り、列挙体メンバーの名前の変更は互換性に影響します。Changing the name of an enumeration member is breaking, unless its contract name is kept the same as in the old version by using the EnumMemberAttribute attribute. 詳細については、「 データコントラクトの列挙型」を参照してください。For more information, see Enumeration Types in Data Contracts.

コレクションCollections

コレクション型の大半はデータ コントラクト モデル内で交換可能であるため、多くの場合、コレクションの変更は互換性に影響しません。Most collection changes are nonbreaking because most collection types are interchangeable with each other in the data contract model. ただし、カスタマイズされていないコレクションからカスタマイズされたコレクションへの変更またはその逆の変更は、互換性に影響する変更です。However, making a noncustomized collection customized or vice versa is a breaking change. また、コレクションのカスタマイズ設定の変更 (データ コントラクトの名前と名前空間の変更、要素名、キー要素名、および値要素名の反復) も互換性に影響します。Also, changing the collection's customization settings is a breaking change; that is, changing its data contract name and namespace, repeating element name, key element name, and value element name. コレクションのカスタマイズの詳細については、「 データコントラクトのコレクション型」を参照してください。For more information about collection customization, see Collection Types in Data Contracts.
また、コレクションの内容のデータ コントラクトの変更 (整数のリストから文字列のリストへの変更など) は互換性に影響する変更です。Naturally, changing the data contract of contents of a collection (for example, changing from a list of integers to a list of strings) is a breaking change.

関連項目See also