Przechowywanie wersji kontraktów danych

W miarę rozwoju aplikacji może być również konieczne zmianę kontraktów danych używanych przez usługi. W tym temacie opisano sposób wersjonowania kontraktów danych. W tym temacie opisano mechanizmy przechowywania wersji kontraktu danych. Aby uzyskać pełne omówienie i normatywne wskazówki dotyczące przechowywania wersji, zobacz Best Practices: Data Contract Versioning (Najlepsze rozwiązania: przechowywanie wersji kontraktu danych).

Zmiany powodujące niezgodność a niezwiązane

Zmiany kontraktu danych mogą być przerywane lub niezwiązane. Gdy kontrakt danych zostanie zmieniony w sposób niezgodny, aplikacja korzystająca ze starszej wersji kontraktu może komunikować się z aplikacją przy użyciu nowszej wersji, a aplikacja korzystająca z nowszej wersji kontraktu może komunikować się z aplikacją przy użyciu starszej wersji. Z drugiej strony zmiana powodująca niezgodność uniemożliwia komunikację w jednym lub obu kierunkach.

Wszelkie zmiany typu, które nie mają wpływu na sposób przesyłania i odbierania, są niezgodne. Takie zmiany nie zmieniają kontraktu danych, tylko typu bazowego. Na przykład można zmienić nazwę pola w sposób niezwiązany, jeśli następnie ustawisz Name właściwość na DataMemberAttribute starszą nazwę wersji. Poniższy kod przedstawia wersję 1 kontraktu danych.

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

Poniższy kod przedstawia niezwiązaną zmianę.

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

Niektóre zmiany modyfikują przesyłane dane, ale mogą lub nie są przerywane. Następujące zmiany są zawsze powodujące niezgodność:

  • Name Zmiana wartości lub Namespace kontraktu danych.

  • Zmiana kolejności elementów członkowskich danych przy użyciu Order właściwości DataMemberAttribute.

  • Zmiana nazwy elementu członkowskiego danych.

  • Zmiana kontraktu danych elementu członkowskiego danych. Na przykład zmiana typu elementu członkowskiego danych z liczby całkowitej na ciąg lub z typu z kontraktem danych o nazwie "Klient" na typ z kontraktem danych o nazwie "Person".

Możliwe są również następujące zmiany.

Dodawanie i usuwanie elementów członkowskich danych

W większości przypadków dodawanie lub usuwanie elementu członkowskiego danych nie jest zmianą powodującą niezgodność, chyba że wymagasz ścisłej ważności schematu (nowe wystąpienia weryfikacji względem starego schematu).

Jeśli typ z dodatkowym polem jest deserializowany do typu z brakującym polem, dodatkowe informacje są ignorowane. (Może być również przechowywany w celach okrężnych; aby uzyskać więcej informacji, zobacz Kontrakty danych zgodne z przekazywaniem).

Gdy typ z brakującym polem jest deserializowany do typu z dodatkowym polem, dodatkowe pole pozostaje w wartości domyślnej, zwykle zero lub null. (Wartość domyślna może zostać zmieniona; aby uzyskać więcej informacji, zobacz Wywołania zwrotne serializacji odporne na wersje).

Można na przykład użyć CarV1 klasy na kliencie i CarV2 klasie w usłudze lub użyć CarV1 klasy w usłudze i CarV2 klasy na kliencie.

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

Punkt końcowy w wersji 2 może pomyślnie wysłać dane do punktu końcowego wersji 1. Serializowanie wersji 2 kontraktu Car danych daje kod XML podobny do poniższego.

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

Aparat deserializacji w wersji 1 nie znajduje pasującego elementu członkowskiego danych dla HorsePower pola i odrzuca te dane.

Ponadto punkt końcowy wersji 1 może wysyłać dane do punktu końcowego wersji 2. Serializowanie wersji 1 kontraktu Car danych daje kod XML podobny do poniższego.

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

Deserializator w wersji 2 nie wie, na co należy ustawić HorsePower pole, ponieważ nie ma pasujących danych w przychodzącym kodzie XML. Zamiast tego pole jest ustawione na wartość domyślną 0.

Wymagane elementy członkowskie danych

Element członkowski danych może być oznaczony jako wymagany przez ustawienie IsRequired właściwości DataMemberAttribute na true. Jeśli brakuje wymaganych danych podczas deserializacji, wyjątek jest zgłaszany zamiast ustawiania elementu członkowskiego danych na wartość domyślną.

Dodanie wymaganego elementu członkowskiego danych to zmiana powodująca niezgodność. Oznacza to, że nowszy typ można nadal wysyłać do punktów końcowych o starszym typie, ale nie w inny sposób. Usunięcie elementu członkowskiego danych oznaczonego jako wymagane w poprzedniej wersji jest również zmianą powodującą niezgodność.

IsRequired Zmiana wartości właściwości z na truefalse nie jest niezgodna, ale zmiana jej z false na true może być niezgodna, jeśli jakiekolwiek wcześniejsze wersje typu nie mają danego elementu członkowskiego danych.

Uwaga

IsRequired Mimo że właściwość jest ustawiona na truewartość , dane przychodzące mogą mieć wartość null lub zero, a typ musi być przygotowany do obsługi tej możliwości. Nie należy używać IsRequired jako mechanizmu zabezpieczeń, aby chronić przed nieprawidłowymi danymi przychodzącymi.

Pominięto wartości domyślne

Istnieje możliwość ustawienia właściwości atrybutu EmitDefaultValue DataMemberAttribute na wartość , zgodnie z opisem w temacie falseWartości domyślne elementu członkowskiego danych. Jeśli to ustawienie to false, element członkowski danych nie będzie emitowany, jeśli jest ustawiony na wartość domyślną (zazwyczaj null lub zero). Nie jest to zgodne z wymaganymi elementami członkowskimi danych w różnych wersjach na dwa sposoby:

  • Kontrakt danych z elementem członkowskim danych wymaganym w jednej wersji nie może odbierać domyślnych (null lub zero) danych z innej wersji, w której element członkowski danych ma EmitDefaultValue ustawioną wartość false.

  • Wymagany element członkowski danych, który ma EmitDefaultValue ustawioną wartość false nie może być używany do serializacji wartości domyślnej (null lub zero), ale może otrzymać taką wartość deserializacji. Powoduje to problem z zaokrąglaniem (dane mogą być odczytywane, ale te same dane nie mogą być następnie zapisywane). W związku z tym, jeśli IsRequired jest true i EmitDefaultValue jest false w jednej wersji, ta sama kombinacja powinna mieć zastosowanie do wszystkich innych wersji, tak aby żadna wersja kontraktu danych nie mogła wygenerować wartości, która nie powoduje rundy.

Zagadnienia dotyczące schematu

Aby uzyskać wyjaśnienie, jaki schemat jest generowany dla typów kontraktów danych, zobacz Dokumentacja schematu kontraktu danych.

Schemat WCF tworzy dla typów kontraktów danych nie zapewnia obsługi wersji. Oznacza to, że schemat wyeksportowany z określonej wersji typu zawiera tylko te elementy członkowskie danych obecne w tej wersji. Implementacja interfejsu IExtensibleDataObject nie zmienia schematu typu.

Składowe danych są domyślnie eksportowane do schematu jako elementy opcjonalne. Oznacza to, że minOccurs wartość (atrybut XML) jest ustawiona na 0. Wymagane elementy członkowskie danych są eksportowane z ustawioną wartością minOccurs 1.

Wiele zmian uważanych za niezgodne faktycznie łamie, jeśli wymagane jest ścisłe przestrzeganie schematu. W poprzednim przykładzie CarV1 wystąpienie z tylko Model elementem będzie weryfikować względem schematu CarV2 (który ma zarówno Model , jak i Horsepower, ale oba są opcjonalne). Jednak odwrotna sytuacja nie jest prawdziwa: wystąpienie zakończy się niepowodzeniem CarV2 weryfikacji względem schematu CarV1 .

Round-tripping wiąże się również z pewnymi dodatkowymi zagadnieniami. Aby uzyskać więcej informacji, zobacz sekcję "Zagadnienia dotyczące schematu" w temacie Forward-Compatible Data Contracts (Kontrakty danych zgodne z przekazywaniem).

Inne dozwolone zmiany

Implementowanie interfejsu IExtensibleDataObject to przełomowa zmiana. Jednak obsługa zaokrąglania nie istnieje dla wersji typu wcześniejszego niż wersja, w której IExtensibleDataObject została zaimplementowana. Aby uzyskać więcej informacji, zobacz Kontrakty danych zgodne z przekazywaniem.

Wyliczenia

Dodawanie lub usuwanie elementu członkowskiego wyliczenia jest zmianą powodującą niezgodność. Zmiana nazwy elementu członkowskiego wyliczenia jest niezgodna, chyba że jego nazwa kontraktu jest taka sama jak w starej wersji przy użyciu atrybutu EnumMemberAttribute . Aby uzyskać więcej informacji, zobacz Typy wyliczenia w kontraktach danych.

Kolekcje

Większość zmian kolekcji jest niezgodna, ponieważ większość typów kolekcji jest zamienna ze sobą w modelu kontraktu danych. Jednak dokonanie niestandardowej kolekcji niestandardowej lub odwrotnie jest zmianą powodującą niezgodność. Ponadto zmiana ustawień dostosowywania kolekcji to zmiana powodująca niezgodność; oznacza to zmianę nazwy kontraktu danych i przestrzeni nazw, powtarzanie nazwy elementu, nazwy elementu klucza i nazwy elementu wartości. Aby uzyskać więcej informacji na temat dostosowywania kolekcji, zobacz Typy kolekcji w kontraktach danych.
Oczywiście zmiana kontraktu danych zawartości kolekcji (na przykład zmiana z listy liczb całkowitych na listę ciągów) jest zmianą powodującą niezgodność.

Zobacz też