Controle de versão de contrato de dados

À medida que os aplicativos evoluem, você também pode ter que alterar os contratos de dados que os serviços usam. Este tópico explica como fazer a versão de contratos de dados. Este tópico descreve os mecanismos de controle de versão de contrato de dados. Para obter uma visão geral completa e orientações de controle de versão prescritivo, consulte Práticas recomendadas: controle de versão de contrato de dados.

Quebrando vs. Alterações Ininterruptas

As alterações a um contrato de dados podem ser ininterruptas ou ininterruptas. Quando um contrato de dados é alterado de forma ininterrupta, um aplicativo usando a versão mais antiga do contrato pode se comunicar com um aplicativo usando a versão mais recente, e um aplicativo usando a versão mais recente do contrato pode se comunicar com um aplicativo usando a versão mais antiga. Por outro lado, uma mudança de quebra impede a comunicação em uma ou ambas as direções.

Quaisquer alterações a um tipo que não afetem a forma como ele é transmitido e recebido são ininterruptas. Tais alterações não alteram o contrato de dados, apenas o tipo subjacente. Por exemplo, você pode alterar o nome de um campo de forma ininterrupta se, em seguida, definir a Name propriedade do para o nome da DataMemberAttribute versão mais antiga. O código a seguir mostra a versão 1 de um contrato de dados.

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

O código a seguir mostra uma alteração ininterrupta.

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

Algumas alterações modificam os dados transmitidos, mas podem ou não estar quebrando. As seguintes alterações estão sempre quebrando:

  • Alterar o ou Namespace o Name valor de um contrato de dados.

  • Alterando a ordem dos membros de dados usando a Order propriedade do DataMemberAttribute.

  • Renomeando um membro de dados.

  • Alterar o contrato de dados de um membro de dados. Por exemplo, alterar o tipo de membro de dados de um inteiro para uma cadeia de caracteres ou de um tipo com um contrato de dados chamado "Cliente" para um tipo com um contrato de dados chamado "Pessoa".

As seguintes alterações também são possíveis.

Adicionando e removendo membros de dados

Na maioria dos casos, adicionar ou remover um membro de dados não é uma alteração de quebra, a menos que você exija uma validade estrita do esquema (novas instâncias validadas em relação ao esquema antigo).

Quando um tipo com um campo extra é desserializado em um tipo com um campo ausente, as informações extras são ignoradas. (Também pode ser armazenado para fins de ida e volta; para obter mais informações, consulte Contratos de Dados Compatíveis com o Encaminhamento).

Quando um tipo com um campo ausente é desserializado em um tipo com um campo extra, o campo extra é deixado em seu valor padrão, geralmente zero ou null. (O valor padrão pode ser alterado; para obter mais informações, consulte Retornos de chamada de serialização tolerantes à versão.)

Por exemplo, você pode usar a CarV1 classe em um cliente e a CarV2 classe em um serviço, ou você pode usar a CarV1 classe em um serviço e a CarV2 classe em um cliente.

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

O ponto de extremidade da versão 2 pode enviar dados com êxito para o ponto de extremidade da versão 1. A serialização da versão 2 do contrato de Car dados produz XML semelhante ao seguinte.

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

O mecanismo de desserialização na V1 não encontra um membro de dados correspondente para o HorsePower campo e descarta esses dados.

Além disso, o ponto de extremidade da versão 1 pode enviar dados para o ponto de extremidade da versão 2. A serialização da versão 1 do contrato de Car dados produz XML semelhante ao seguinte.

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

O desserializador versão 2 não sabe para que definir o HorsePower campo, porque não há dados correspondentes no XML de entrada. Em vez disso, o campo é definido como o valor padrão de 0.

Membros de dados necessários

Um membro de dados pode ser marcado como sendo obrigatório definindo a IsRequired propriedade do DataMemberAttribute para true. Se os dados necessários estiverem ausentes durante a desserialização, uma exceção será lançada em vez de definir o membro de dados para seu valor padrão.

Adicionar um membro de dados necessário é uma alteração importante. Ou seja, o tipo mais recente ainda pode ser enviado para pontos de extremidade com o tipo mais antigo, mas não o contrário. Remover um membro de dados que foi marcado como necessário em qualquer versão anterior também é uma alteração importante.

Alterar o IsRequired valor da propriedade de true para false não é quebrar, mas alterá-lo de para true pode estar quebrando se alguma versão anterior do tipo não tiver o membro de false dados em questão.

Nota

Embora a IsRequired propriedade esteja definida como true, os dados de entrada podem ser nulos ou zero, e um tipo deve ser preparado para lidar com essa possibilidade. Não use IsRequired como um mecanismo de segurança para proteger contra dados de entrada incorretos.

Valores padrão omitidos

É possível (embora não recomendado) definir a EmitDefaultValue propriedade no atributo DataMemberAttribute como , conforme falsedescrito em Data Member Default Values. Se essa configuração for false, o membro de dados não será emitido se estiver definido como seu valor padrão (geralmente nulo ou zero). Isso não é compatível com os membros de dados necessários em versões diferentes de duas maneiras:

  • Um contrato de dados com um membro de dados que é necessário em uma versão não pode receber dados padrão (nulos ou zero) de uma versão diferente na qual o membro de dados definiu EmitDefaultValue como false.

  • Um membro de dados necessário que tenha EmitDefaultValue definido como false não pode ser usado para serializar seu valor padrão (nulo ou zero), mas pode receber esse valor na desserialização. Isso cria um problema de ida e volta (os dados podem ser lidos, mas os mesmos dados não podem ser gravados). Por conseguinte, se estiver EmitDefaultValuetrue e estiver false numa versão, a mesma combinação deve aplicar-se IsRequired a todas as outras versões, de modo a que nenhuma versão do contrato de dados possa produzir um valor que não resulte numa viagem de ida e volta.

Considerações sobre o esquema

Para obter uma explicação de qual esquema é produzido para tipos de contrato de dados, consulte Referência de esquema de contrato de dados.

O esquema que o WCF produz para tipos de contrato de dados não faz provisões para controle de versão. Ou seja, o esquema exportado de uma determinada versão de um tipo contém apenas os membros de dados presentes nessa versão. A implementação da IExtensibleDataObject interface não altera o esquema de um tipo.

Os membros de dados são exportados para o esquema como elementos opcionais por padrão. Ou seja, o minOccurs valor (atributo XML) é definido como 0. Os membros de dados necessários são exportados com minOccurs o definido como 1.

Muitas das alterações consideradas ininterruptas são, na verdade, quebradas se for necessária uma adesão estrita ao esquema. No exemplo anterior, uma CarV1 instância com apenas o elemento seria validada Model em relação ao CarV2 esquema (que tem ambos Model e Horsepower, mas ambos são opcionais). No entanto, o inverso não é verdadeiro: uma CarV2 instância falharia na validação em relação ao CarV1 esquema.

A viagem de ida e volta também implica algumas considerações adicionais. Para obter mais informações, consulte a seção "Considerações sobre o esquema" em Contratos de dados compatíveis com o encaminhamento.

Outras alterações permitidas

A implementação da IExtensibleDataObject interface é uma mudança ininterrupta. No entanto, o suporte de ida e volta não existe para versões do tipo anteriores à versão em que IExtensibleDataObject foi implementado. Para obter mais informações, consulte Contratos de dados compatíveis com encaminhamento.

Enumerações

Adicionar ou remover um membro de enumeração é uma alteração de quebra. Alterar o nome de um membro de enumeração é quebrar, a menos que seu nome de contrato seja mantido o mesmo que na versão antiga usando o EnumMemberAttribute atributo. Para obter mais informações, consulte Tipos de enumeração em contratos de dados.

Coleções

A maioria das alterações de coleta é ininterrupta porque a maioria dos tipos de coleta são intercambiáveis entre si no modelo de contrato de dados. No entanto, tornar uma coleção não personalizada ou vice-versa é uma mudança revolucionária. Além disso, alterar as configurações de personalização da coleção é uma mudança importante; ou seja, alterando o nome do contrato de dados e o namespace, repetindo o nome do elemento, o nome do elemento chave e o nome do elemento value. Para obter mais informações sobre a personalização da coleção, consulte Tipos de coleção em contratos de dados.
Naturalmente, alterar o contrato de dados do conteúdo de uma coleção (por exemplo, mudar de uma lista de inteiros para uma lista de cadeias de caracteres) é uma alteração de quebra.

Consulte também