Versionshantering av datakontrakt
När programmen utvecklas kan du också behöva ändra de datakontrakt som tjänsterna använder. Det här avsnittet beskriver hur du skapar versionsdatakontrakt. I det här avsnittet beskrivs mekanismerna för versionshantering av datakontrakt. En fullständig översikt och vägledning om normativ versionshantering finns i Metodtips: Versionshantering för datakontrakt.
Icke-bakåtkompatibla ändringar
Ändringar i ett datakontrakt kan vara icke-bakåtkompatibla. När ett datakontrakt ändras på ett icke-banbrytande sätt kan ett program som använder den äldre versionen av kontraktet kommunicera med ett program med den nyare versionen, och ett program som använder den nyare versionen av kontraktet kan kommunicera med ett program med den äldre versionen. Å andra sidan förhindrar en icke-bakåtkompatibel ändring kommunikationen i en eller båda riktningarna.
Alla ändringar av en typ som inte påverkar hur den överförs och tas emot är icke-inbrytbara. Sådana ändringar ändrar inte datakontraktet, bara den underliggande typen. Du kan till exempel ändra namnet på ett fält på ett icke-inbrytande sätt om du sedan anger Name egenskapen DataMemberAttribute för till det äldre versionsnamnet. Följande kod visar version 1 av ett datakontrakt.
// Version 1
[DataContract]
public class Person
{
[DataMember]
private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
<DataMember()> _
Private Phone As String
End Class
Följande kod visar en icke-inbrytningsändring.
// 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
Vissa ändringar ändrar de överförda data, men det kan hända att de inte går sönder. Följande ändringar bryter alltid:
Ändra ordningen på datamedlemmar med hjälp Order av egenskapen för DataMemberAttribute.
Byta namn på en datamedlem.
Ändra datakontraktet för en datamedlem. Du kan till exempel ändra typen av datamedlem från ett heltal till en sträng, eller från en typ med ett datakontrakt med namnet "Kund" till en typ med ett datakontrakt med namnet "Person".
Följande ändringar är också möjliga.
Lägga till och ta bort datamedlemmar
I de flesta fall är det inte en icke-bakåtkompatibel ändring att lägga till eller ta bort en datamedlem, såvida du inte behöver strikt schema giltighet (nya instanser validerar mot det gamla schemat).
När en typ med ett extra fält deserialiseras till en typ med ett fält som saknas ignoreras den extra informationen. (Den kan också lagras i tur- och retursyfte. Mer information finns iFramåtkompatibla datakontrakt).
När en typ med ett fält som saknas deserialiseras till en typ med ett extra fält, lämnas det extra fältet vid standardvärdet, vanligtvis noll eller null
. (Standardvärdet kan ändras. Mer information finns i Versionstoleranta återanrop för serialisering.)
Du kan till exempel använda CarV1
klassen på en klient och CarV2
klassen på en tjänst, eller så kan du använda CarV1
klassen på en tjänst och CarV2
klassen på en klient.
// 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
Version 2-slutpunkten kan skicka data till slutpunkten version 1. Serialisering av version 2 av datakontraktet Car
ger XML som liknar följande.
<Car>
<Model>Porsche</Model>
<HorsePower>300</HorsePower>
</Car>
Deserialiseringsmotorn på V1 hittar ingen matchande datamedlem för HorsePower
fältet och tar bort dessa data.
Dessutom kan version 1-slutpunkten skicka data till slutpunkten version 2. Serialisering av version 1 av datakontraktet Car
ger XML som liknar följande.
<Car>
<Model>Porsche</Model>
</Car>
Version 2 deserializer vet inte vad fältet HorsePower
ska anges till eftersom det inte finns några matchande data i inkommande XML. I stället är fältet inställt på standardvärdet 0.
Obligatoriska datamedlemmar
En datamedlem kan markeras som obligatorisk genom att ange IsRequired egenskapen DataMemberAttribute för till true
. Om nödvändiga data saknas vid deserialisering genereras ett undantag i stället för att datamedlemmen anges till standardvärdet.
Att lägga till en obligatorisk datamedlem är en icke-bakåtkompatibel ändring. Den nyare typen kan alltså fortfarande skickas till slutpunkter med den äldre typen, men inte tvärtom. Att ta bort en datamedlem som har markerats som obligatorisk i en tidigare version är också en icke-bakåtkompatibel ändring.
Det går inte att ändra egenskapsvärdet IsRequired från true
till false
, men att ändra det från false
till true
kan vara icke-bakåtkompatibelt om någon tidigare version av typen inte har den aktuella datamedlemmen.
Kommentar
Även om egenskapen IsRequired är inställd på true
kan inkommande data vara null eller noll, och en typ måste vara beredd att hantera den här möjligheten. Använd inte IsRequired som en säkerhetsmekanism för att skydda mot felaktiga inkommande data.
Utelämnade standardvärden
Det är möjligt (men rekommenderas inte) att ange EmitDefaultValue
egenskapen för attributet DataMemberAttribute till false
, enligt beskrivningen i Standardvärden för datamedlem. Om den här inställningen är false
genereras inte datamedlemmen om den är inställd på sitt standardvärde (vanligtvis null eller noll). Detta är inte kompatibelt med obligatoriska datamedlemmar i olika versioner på två sätt:
Ett datakontrakt med en datamedlem som krävs i en version kan inte ta emot standarddata (null eller noll) från en annan version där datamedlemmen har
EmitDefaultValue
angett tillfalse
.En obligatorisk datamedlem som har
EmitDefaultValue
angetts tillfalse
kan inte användas för att serialisera standardvärdet (null eller noll), men kan ta emot ett sådant värde vid deserialisering. Detta skapar ett problem med rundkörning (data kan läsas in men samma data kan sedan inte skrivas ut).IsRequired
Om ärtrue
ochEmitDefaultValue
finnsfalse
i en version bör därför samma kombination gälla för alla andra versioner så att ingen version av datakontraktet skulle kunna generera ett värde som inte resulterar i en tur och retur-resa.
Schemaöverväganden
En förklaring av vilket schema som skapas för datakontraktstyper finns i Referens för datakontraktsschema.
Schemat som WCF skapar för datakontraktstyper gör inga avsättningar för versionshantering. Schemat som exporteras från en viss version av en typ innehåller alltså endast de datamedlemmar som finns i den versionen. Implementeringen av IExtensibleDataObject gränssnittet ändrar inte schemat för en typ.
Datamedlemmar exporteras till schemat som valfria element som standard. minOccurs
Värdet (XML-attribut) är alltså inställt på 0. Nödvändiga datamedlemmar exporteras med minOccurs
värdet 1.
Många av de ändringar som anses vara icke-inbrytande bryter faktiskt om strikt efterlevnad av schemat krävs. I föregående exempel verifierar en CarV1
instans med bara elementet Model
mot CarV2
schemat (som har både Model
och Horsepower
, men båda är valfria). Det omvända är dock inte sant: en CarV2
instans skulle misslyckas med valideringen mot CarV1
schemat.
Rund-tripping medför också några ytterligare överväganden. Mer information finns i avsnittet "Schemaöverväganden" i Framåtkompatibla datakontrakt.
Andra tillåtna ändringar
Implementering av IExtensibleDataObject gränssnittet är en icke-banbrytande ändring. Stöd för avkörning finns dock inte för versioner av typen före den version som IExtensibleDataObject implementerades. Mer information finns i Framåtkompatibla datakontrakt.
Uppräkningar
Att lägga till eller ta bort en uppräkningsmedlem är en icke-bakåtkompatibel ändring. Det går inte att ändra namnet på en uppräkningsmedlem, såvida inte dess kontraktsnamn behålls på samma sätt som i den gamla versionen med hjälp EnumMemberAttribute
av attributet . Mer information finns i Uppräkningstyper i datakontrakt.
Samlingar
De flesta samlingsändringar är icke-brytbara eftersom de flesta samlingstyper är utbytbara med varandra i datakontraktsmodellen. Att göra en icke-anpassad samling anpassad eller vice versa är dock en icke-anpassad ändring. Dessutom är det en icke-bakåtkompatibel ändring att ändra inställningarna för samlingens anpassning. det vill säga att ändra dess namn och namnområde för datakontrakt, upprepande elementnamn, nyckelelementnamn och värdeelementnamn. Mer information om insamlingsanpassning finns i Samlingstyper i Datakontrakt.
Att ändra datakontraktet för innehållet i en samling (till exempel att ändra från en lista med heltal till en lista med strängar) är naturligtvis en icke-bakåtkompatibel ändring.