アプリケーションが進化するにつれて、サービスが使用するデータ コントラクトを変更する必要がある場合もあります。 このトピックでは、データ コントラクトをバージョン管理する方法について説明します。 このトピックでは、データ コントラクトのバージョン管理メカニズムについて説明します。 完全な概要と規範的なバージョン管理ガイダンスについては、「 ベスト プラクティス: データ コントラクトのバージョン管理」を参照してください。
破壊的変更と非破壊的変更
データ コントラクトに対する変更は、破壊的または非破壊的である可能性があります。 データ コントラクトが壊れない方法で変更されると、以前のバージョンのコントラクトを使用するアプリケーションは、新しいバージョンを使用してアプリケーションと通信でき、コントラクトの新しいバージョンを使用するアプリケーションは、古いバージョンを使用してアプリケーションと通信できます。 一方、破壊的変更は、一方向または両方向の通信を防止します。
型が送受信される方法に影響を与えない変更は、非改行です。 このような変更によってデータ コントラクトは変更されず、基になる型のみが変更されます。 たとえば、DataMemberAttributeのName プロパティを以前のバージョン名に設定すると、フィールドの名前を改行しない方法で変更できます。 次のコードは、データ コントラクトのバージョン 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
一部の変更では、送信されるデータは変更されますが、中断される場合とそうでない場合があります。 次の変更は常に重大です。
DataMemberAttributeの Order プロパティを使用してデータ メンバーの順序を変更する。
データ メンバーの名前を変更する。
データ メンバーのデータ コントラクトを変更する。 たとえば、データ メンバーの型を整数から文字列に変更したり、"Customer" という名前のデータ コントラクトを持つ型から、"Person" という名前のデータ コントラクトを持つ型に変更したりします。
次の変更も可能です。
データ メンバーの追加と削除
ほとんどの場合、厳密なスキーマの有効性 (古いスキーマに対して検証する新しいインスタンス) が必要でない限り、データ メンバーの追加または削除は重大な変更ではありません。
余分なフィールドを持つ型が、フィールドがない型に逆シリアル化された場合、追加情報は無視されます。 (ラウンドトリップの目的で格納される場合もあります。詳細については、「 Forward-Compatible データ コントラクト」を参照してください)。
フィールドが見つからない型が、追加のフィールドを持つ型に逆シリアル化された場合、追加のフィールドは既定値 (通常は 0 または 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 のエンドポイントにデータを正常に送信できます。
Car
データ コントラクトのバージョン 2 をシリアル化すると、次のような XML が生成されます。
<Car>
<Model>Porsche</Model>
<HorsePower>300</HorsePower>
</Car>
V1 の逆シリアル化エンジンは、 HorsePower
フィールドの一致するデータ メンバーを見つけず、そのデータを破棄します。
また、バージョン 1 のエンドポイントは、バージョン 2 のエンドポイントにデータを送信できます。
Car
データ コントラクトのバージョン 1 をシリアル化すると、次のような XML が生成されます。
<Car>
<Model>Porsche</Model>
</Car>
バージョン 2 のデシリアライザーは、受信 XML に一致するデータがないため、 HorsePower
フィールドを設定する対象を認識しません。 代わりに、フィールドは既定値の 0 に設定されます。
必須データ メンバー
データ メンバーは、DataMemberAttributeの IsRequired プロパティを true
に設定することで、必須としてマークされる場合があります。 逆シリアル化中に必要なデータが見つからない場合は、データ メンバーを既定値に設定する代わりに例外がスローされます。
必要なデータ メンバーを追加することは重大な変更です。 つまり、新しい型は古い型のエンドポイントに引き続き送信できますが、それ以外の方法では送信できません。 以前のバージョンで必須としてマークされたデータ メンバーを削除することは、重大な変更でもあります。
IsRequiredプロパティの値をtrue
からfalse
に変更しても互換性は損なわれませんが、以前のバージョンの型にデータ メンバーがない場合は、false
からtrue
に変更すると中断する可能性があります。
注
IsRequired プロパティは true
に設定されていますが、受信データは null またはゼロの場合があり、この可能性を処理するには型を準備する必要があります。 不正な受信データから保護するためのセキュリティ メカニズムとして IsRequired を使用しないでください。
省略された既定値
DataMemberAttribute 属性のEmitDefaultValue
プロパティを「データ メンバーの既定値」で説明されているように、false
に設定することは可能です (推奨されません)。 この設定が false
場合、既定値 (通常は null またはゼロ) に設定されている場合、データ メンバーは出力されません。 これは、次の 2 つの方法で異なるバージョンの必要なデータ メンバーと互換性がありません。
1 つのバージョンで必要なデータ メンバーを持つデータ コントラクトは、データ メンバーが
false
に設定されている別のバージョンの既定の (null またはゼロ) データEmitDefaultValue
受け取ることができません。false
に設定EmitDefaultValue
必要なデータ メンバーは、既定値 (null またはゼロ) のシリアル化には使用できませんが、逆シリアル化時にこのような値を受け取ることができます。 これにより、ラウンドトリップの問題が発生します (データは読み取ることができますが、同じデータを書き出すことはできません)。 したがって、IsRequired
がtrue
され、EmitDefaultValue
が 1 つのバージョンでfalse
されている場合は、他のすべてのバージョンに同じ組み合わせを適用する必要があります。そのため、データ コントラクトのバージョンがラウンド トリップにならない値を生成することはできません。
スキーマに関する考慮事項
データ コントラクト型に対して生成されるスキーマの詳細については、「 データ コントラクト スキーマリファレンス」を参照してください。
WCF がデータ コントラクト型に対して生成するスキーマでは、バージョン管理のプロビジョニングは行われません。 つまり、特定のバージョンの型からエクスポートされたスキーマには、そのバージョンに存在するデータ メンバーのみが含まれます。 IExtensibleDataObject インターフェイスを実装しても、型のスキーマは変更されません。
既定では、データ メンバーは省略可能な要素としてスキーマにエクスポートされます。 つまり、 minOccurs
(XML 属性) の値は 0 に設定されます。 必要なデータ メンバーは、 minOccurs
1 に設定してエクスポートされます。
スキーマへの厳密な準拠が必要な場合、非破壊的と見なされる変更の多くは実際には壊れます。 前の例では、Model
要素のみを持つCarV1
インスタンスは、CarV2
スキーマに対して検証されます (Model
とHorsepower
の両方がありますが、どちらも省略可能です)。 ただし、逆は正しくありません。 CarV2
インスタンスは、 CarV1
スキーマに対する検証に失敗します。
ラウンドトリップには、いくつかの追加の考慮事項も伴います。 詳細については、「 Forward-Compatible データ コントラクト」の「スキーマに関する考慮事項」セクションを参照してください。
その他の許可される変更
IExtensibleDataObject インターフェイスの実装は、画期的な変更です。 ただし、 IExtensibleDataObject が実装されたバージョンより前の型のバージョンでは、ラウンドトリップ サポートは存在しません。 詳細については、「 Forward-Compatible データ コントラクト」を参照してください。
列挙
列挙メンバーの追加または削除は重大な変更です。
EnumMemberAttribute
属性を使用してコントラクト名が古いバージョンと同じに保たれている場合を除き、列挙メンバーの名前の変更は中断されます。 詳細については、「 データ コントラクトの列挙型」を参照してください。
コレクション
ほとんどのコレクション型はデータ コントラクト モデル内で相互に交換可能であるため、ほとんどのコレクションの変更は壊れないです。 ただし、カスタマイズされていないコレクションをカスタマイズするか、またはその逆にすることは重大な変更です。 また、コレクションのカスタマイズ設定を変更することは重大な変更です。つまり、データ コントラクト名と名前空間、繰り返し要素名、キー要素名、および値要素名を変更します。 コレクションのカスタマイズの詳細については、「 データ コントラクトのコレクション型」を参照してください。
当然ながら、コレクションの内容のデータ コントラクトを変更することは (たとえば、整数のリストから文字列のリストに変更する)、重大な変更です。