Datenvertragsversionsverwaltung

Durch die Weiterentwicklung der Anwendungen müssen Sie möglicherweise auch die Datenverträge ändern, die die Dienste verwenden. Dieses Thema erklärt, wie man die Versionsverwaltung von Datenverträgen durchführt. In diesem Thema werden die Datenvertragsversionsmechanismen beschrieben. Eine vollständige Übersicht und normative Versionsverwaltungsanleitung finden Sie unter Empfohlene Vorgehensweisen: Versionsverwaltung von Datenverträgen.

Breaking Changes gegenüber Non-Breaking Changes

Änderungen an einem Datenvertrag können "breaking" oder "non-breaking" sein. Wird ein Datenvertrag auf eine "non-breaking"-Art geändert, kann eine Anwendung, die die ältere Version des Vertrags verwendet, mit einer Anwendung kommunizieren, die die neuere Version verwendet; und eine Anwendung, die die neuere Version des Vertrags verwendet, kann mit einer Anwendung kommunizieren, die die ältere Version verwendet. Andererseits verhindert ein "Breaking Change" die Kommunikation in eine oder beide Richtungen.

Jede Änderung eines Typs, der nicht beeinflusst, wie er gesendet und empfangen wird, ist "non-breaking". Solche Änderungen ändern nicht den Datenvertrag, nur den zugrunde liegenden Typ. Beispielsweise kann der Name eines Felds auf "non-breaking"-Art geändert werden, indem die Eigenschaft Name des DataMemberAttribute auf den älteren Versionsnamen festgelegt wird. Der folgende Code zeigt Version 1 eines Datenvertrags.

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

Im folgenden Code wird ein "Non-Breaking Change" veranschaulicht.

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

Einige Änderungen ändern die gesendeten Daten, müssen aber nicht unbedingt "breaking" sein. Die folgenden Änderungen sind immer "breaking":

  • Das Ändern der Werte Nameoder Namespace eines Datenvertrags.

  • Das Ändern der Reihenfolge der Datenelemente über die Eigenschaft Order der Klasse DataMemberAttribute.

  • Das Umbenennen eines Datenelements.

  • Das Ändern des Datenvertrags eines Datenelements. Beispielsweise die Änderung des Typs eines Datenelements von ganzer Zahl zu Zeichenfolge oder von einem Typ mit Datenvertrag namens "Kunde" in einen Typ mit einem Datenvertrag namens "Person".

Ebenfalls möglich sind die folgenden Änderungen.

Das Hinzufügen und Entfernen von Datenelementen

In den meisten Fällen ist das Hinzufügen oder Entfernen eines Datenelements kein "Breaking Change", sofern keine strikte Schemavalidierung (neue Instanzen, die gegen das alte Schema validiert werden) erforderlich ist.

Wenn ein Typ mit einem Extrafeld in einen Typ mit einem fehlenden Feld deserialisiert wird, werden die Extrainformationen ignoriert. (Möglicherweise werden sie auch für Round-Tripping-Zwecke gespeichert. Weitere Informationen hierzu finden Sie unter Aufwärtskompatible Datenverträge).

Wenn ein Typ mit einem fehlenden Feld in einen Typ mit einem Extrafeld deserialisiert wird, behält das Extrafeld seinen Standardwert bei, normalerweise null. (Der Standardwert kann geändert werden. Weitere Informationen hierzu finden Sie unter Versionstolerante Serialisierungsrückrufe.)

Beispielsweise können Sie die Klasse CarV1 für einen Client und die Klasse CarV2 für einen Dienst verwenden; oder Sie können die Klasse CarV1 für einen Dienst und die Klasse CarV2 für einen Client verwenden.

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

Der Version 2-Endpunkt kann Daten erfolgreich an den Version 1-Endpunkt senden. Eine Serialisierung von Version 2 des Car-Datenvertrags führt zu einer XML-Darstellung, die in etwa der folgenden entspricht.

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

Die Deserialisierungs-Engine auf V1 findet kein entsprechendes Datenelement für das Feld HorsePower und verwirft die Daten.

Außerdem kann der Version 1-Endpunkt Daten erfolgreich an den Version 2-Endpunkt senden. Eine Serialisierung von Version 1 des Car-Datenvertrags führt zu einer der folgenden ähnlichen XML-Darstellung.

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

Der Version 2-Deserialisierer weiß nicht, auf was er das Feld HorsePower festlegen soll, da in der eingehenden XML keine entsprechenden Daten vorliegen. Stattdessen wird das Feld auf den Standardwert 0 (null) festgelegt.

Erforderliche Datenelemente

Ein Datenelement kann als "erforderlich" markiert werden, indem die Eigenschaft IsRequired der Klasse DataMemberAttribute auf true festgelegt wird. Wenn erforderliche Daten während der Deserialisierung fehlen, wird eine Ausnahme verwendet, anstatt die Datenelemente auf ihren Standardwert festzulegen.

Das Hinzufügen von erforderlichen Datenelementen ist ein "Breaking Change". Das bedeutet, dass der neuere Typ noch immer an Endpunkte mit dem älteren Typ gesendet werden kann, aber nicht umgekehrt. Das Löschen eines Datenelements, das in einer vorherigen Version als "erforderlich" markiert war, ist ebenfalls ein "Breaking Change".

Die Änderung des Eigenschaftswerts IsRequired von true nach false ist nicht "breaking", aber die Änderung von false nach true kann "breaking" sein, wenn eine frühere Version des Typs das fragliche Datenelement nicht besitzt.

Hinweis

Auch wenn die Eigenschaft IsRequired auf true festgelegt ist, müssen die eingehenden Daten NULL oder 0 (null) sein, und es muss ein Typ vorbereitet sein, um diese Möglichkeit abzudecken. IsRequired sollte nicht als Sicherheitsmechanismus zum Schutz gegen ungültige eingehende Daten verwendet werden.

Ausgelassene Standardwerte

Es ist möglich (wenn auch nicht empfehlenswert), die EmitDefaultValue-Eigenschaft des DataMemberAttribute-Attributs auf false festzulegen, wie unter Standardwerte der Datenelemente beschrieben wird. Wenn diese Einstellung false ist, wird das Datenelement nicht ausgegeben, wenn es auf seinen Standardwert (normalerweise NULL oder 0 (null)) festgelegt ist. Dies ist in anderen Versionen auf zwei Arten nicht kompatibel mit erforderlichen Datenelementen:

  • Ein Datenvertrag mit einem Datenelement, das in einer Version erforderlich ist, kann keine Standarddaten (NULL oder 0(null)) von einer anderen Version erhalten, in der das Datenelement EmitDefaultValue auf false festgelegt hat.

  • Ein erforderliches Datenelement, dessen EmitDefaultValue auf false festgelegt ist, kann nicht verwendet werden, um seinen Standardwert (NULL oder 0 (null)) zu deserialisieren, aber es kann bei der Deserialisierung einen derartigen Wert erhalten. Dies schafft ein Round-Tripping-Problem (Daten können eingelesen werden, aber die gleichen Daten können nicht ausgegeben werden). Wenn daher in einer Version IsRequiredtrue und EmitDefaultValuefalse ist, sollte die gleiche Kombination für alle anderen Versionen gelten, damit keine Version des Datenvertrags einen Wert produzieren kann, der nicht zu einem Roundtrip führt.

Schemaüberlegungen

Eine Erklärung, welches Schema für welche Datenvertragstypen produziert wird, finden Sie unter Datenvertrags-Schemareferenz.

Das Schema, das WCF für Datenvertragstypen erstellt, macht keine Bereitstellungen für die Versionsverwaltung. Das bedeutet, dass das Schema, das von einer bestimmten Version eines Typs exportiert wurde, nur diejenigen Datenelemente enthält, die in dieser Version vorliegen. Die Implementierung der IExtensibleDataObject-Schnittstelle ändert nicht das Schema eines Typs.

Datenelemente werden standardmäßig als optionale Elemente ins Schema exportiert. Dies bedeutet, dass der Wert von minOccurs (XML-Attribut) auf 0 (null) gesetzt wird. Erforderliche Datenmember werden exportiert, wenn minOccurs auf 1 festgelegt wurde.

Viele der Änderungen, die als "non-breaking" gelten, sind tatsächlich "breaking", wenn eine strenge Einhaltung des Schemas erforderlich ist. Im vorherigen Beispiel würde eine CarV1-Instanz, die nur das Element Model enthält, gegen das CarV2-Schema (das sowohl über Model als auch Horsepower verfügt, die aber beide optional sind) positiv geprüft werden. Das Gegenteil stimmt aber nicht: eine CarV2-Instanz würde keine Validierung gegen das CarV1-Schema bestehen.

Round-Tripping bringt auch einige zusätzliche Überlegungen mit sich. Weitere Informationen finden Sie im Abschnitt "Schemaüberlegungen" unter Aufwärtskompatible Datenverträge.

Andere zulässige Änderungen

Die Implementierung der IExtensibleDataObject-Schnittstelle ist ein "Non-Breaking Change". Es besteht jedoch kein Round-Tripping-Support für Versionen des Typs vor der Version, in der IExtensibleDataObject implementiert wurde. Weitere Informationen finden Sie unter Aufwärtskompatible Datenverträge.

Enumerationen

Das Hinzufügen oder Entfernen einer Enumeration ist ein "Breaking Change". Die Änderung des Namens einer Enumeration ist "breaking", sofern nicht der Vertragsname durch die Verwendung des Attributs EnumMemberAttribute der gleiche wie in der alten Version geblieben ist. Weitere Informationen finden Sie unter Enumerationstypen in Datenverträgen.

Sammlungen

Die meisten Sammlungsänderungen sind "non-breaking", da die meisten Sammlungstypen im Datenvertragsmodell gegeneinander austauschbar sind. Eine nicht angepasste Sammlung angepasst zu machen oder umgekehrt ist jedoch ein "Breaking Change". Außerdem ist die Änderung der Anpassungseinstellungen der Sammlung ein "Breaking Change", d. h. eine Änderung des Namens und Namespace ihres Datenvertrags und eine Wiederholung des Elementnamens, des Schlüsselelementnamens und des Wertelementnamens. Weitere Informationen zur Sammlungsanpassung finden Sie unter Sammlungstypen in Datenverträgen.
Natürlich ist die Änderung des Datenvertrags der Inhalte einer Sammlung (z. B. die Änderung von einer Liste ganzer Zahlen zu einer Liste von Zeichenfolgen) ein "Breaking Change".

Siehe auch