Esta postagem do blog é baseada em um post anterior do blog de Jeff Morris que cobriu a API de subdocumentos enquanto ela ainda estava na versão prévia para desenvolvedores. Houve algumas alterações na API desde esse lançamento.
Com Servidor Couchbase 4.5 e o .NET SDK 2.3.xAgora você pode usar o Subdocumento em seu aplicativo .NET.
Nas versões anteriores do Couchbase, todas as mutações de documentos eram atômicas e envolviam o documento inteiro. Se você quiser alterar apenas um único campo e depois fazer uma atualização, todo o documento no servidor Couchbase será copiado pela nova revisão. O problema é que, se o documento for grande ou a rede for lenta (ou ambos), muitos recursos serão desperdiçados com o envio de dados que não foram modificados. Uma solução melhor e de melhor desempenho seria enviar apenas a parte do documento ou o valor que sofreu mutação. Essencialmente, é isso que você obtém com a API de subdocumento; quando você atualiza um elemento ou exclui um elemento de um documento, somente o caminho do fragmento a ser alterado é enviado pela rede e somente essa parte do documento é modificada.

Há várias operações diferentes que são suportadas pela API, desde mutações em elementos individuais aninhados (também conhecidos como subdocumentos) para modificações em matrizes e dicionários. As operações de contador também são suportadas, assim como as operações de recuperação de fragmentos JSON incorporados.
A API é exposta por meio de um fluente que permite anexar várias operações e executá-las atomicamente no documento. Há dois "construtores" diferentes: um construtor para operações de mutação e um construtor para leituras ou "pesquisas" (que também pode verificar se um elemento existe em um determinado caminho).
Pré-requisito: Couchbase Server 4.5
Para seguir os exemplos abaixo, você precisará fazer o download e instalar Servidor Couchbase 4.5. Se você nunca instalou o Couchbase Server antes, assista ao meu vídeo sobre Como instalar o Couchbase Server no Windows. É muito fácil, independentemente do sistema operacional que estiver usando.
Visão geral da API do subdocumento
Os exemplos a seguir usarão um documento com um ID de "puppy" e começarão com a seguinte aparência:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "tipo": "cachorro", "raça": "Pitbull/Chihuahua", "name" (nome): "Filhote de cachorro", "brinquedos": [ "squeaker", "bola", "sapato" ], "proprietário": { "tipo": "servo", "name" (nome): "Don Knotts", "idade": 63 }, "atributos": { "pulgas": verdadeiro, "cor": "branco", "eyeColor" (cor dos olhos): "marrom", "idade": 5, "sujo": verdadeiro, "sexo": "feminino" } } |
Todos os Os exemplos estão disponíveis no Github para que você possa clonar o projeto e brincar com a API.
MutateInBuilder e LookupInBuilder
A API de subdocumentos oferece dois novos tipos que utilizam um padrão de construtor por meio de uma interface fluente para encadear várias operações em um documento. Ambos os objetos são criados chamando Mutação
ou Pesquisa
em um CouchbaseBucket
e passando a chave do documento com o qual você está trabalhando:
1 2 3 4 5 6 7 8 9 10 11 |
//Inicialize o auxiliar de cluster com as configurações padrão, ou seja, localhost Ajudante de cluster.Inicializar(); var balde = Ajudante de cluster.GetBucket("default"); //criar um construtor de mutação para o documento "thekey" var sofrer mutação = balde.Mutação("thekey"); //criar um construtor de pesquisa para o documento "thekey2" var pesquisa = balde.Pesquisa("thekey2"); Ajudante de cluster.Fechar(); |
Depois de ter o objeto construtor, você pode encadear várias operações para executar no documento, por exemplo:
1 2 3 4 5 |
var construtor = balde.Pesquisa(id). Obter("tipo"). Obter("name" (nome)). Obter("proprietário"). Existe("notfound"); |
Em seguida, você pode enviar todas as operações para o servidor em um único lote:
1 |
var fragmento = construtor.Executar(); |
Você pode verificar o resultado de uma operação usando OpStatus e um caminho. Neste exemplo, estou usando o caminho "tipo"
:
1 2 3 4 5 |
se (fragmento.Status da operação("tipo") == Status da resposta.Sucesso) { string formato = "Path='{0}' Value='{1}'"; Console.WriteLine(formato, "tipo", fragmento.Conteúdo("tipo")); } |
Estes são alguns dos métodos e campos que você encontrará no IDoc DocumentFragment
interface.
Nome |
Descrição |
Conteúdo(...) |
Obtém o conteúdo de um determinado caminho ou índice. |
Existe(...) |
Retorna true se houver um resultado para um determinado caminho ou índice. |
Count() |
O número de operações atuais mantidas pelo construtor. |
OpStatus(...) |
O |
Status |
O |
Sucesso |
Verdadeiro se toda a operação múltipla for bem-sucedida. |
Além dessas propriedades ou métodos, há todas as outras propriedades herdadas de Resultado da operação
(que é a resposta padrão de uma operação de chave/valor): Upsert, Remove, Replace e assim por diante.
Tratamento de erros
Ao enviar vários mutaçõesSe um deles falhar, toda a solicitação de várias operações falhará. Isso permite uma semântica transacional do tipo "tudo ou nada" ao realizar mutações em um único documento.
Ao enviar vários pesquisasSe o servidor tentar retornar tantos itens quantos forem solicitados, algumas operações poderão ser bem-sucedidas e outras poderão falhar.
Se a(s) operação(ões) falhar(em), oStatus
conterá uma resposta de erro de nível superior, como SubDocMultiPathFailure
. Essa é uma indicação de que você deve se aprofundar nos resultados da operação para obter o erro específico. Você pode fazer isso por meio de iteração: chamando a função Status da operação
e passando o índice ou o caminho:
1 2 3 4 5 6 7 8 9 10 11 |
var construtor = balde.Pesquisa(id). Obter("tipo"). Obter("somepaththatdoesntexist" (algum caminho que não existe)). Obter("proprietário"); var fragmento = construtor.Executar(); Console.WriteLine("Erro genérico: {0}{1}Erro específico: {2}", fragmento.Status, Meio ambiente.Nova linha, fragmento.Status da operação(1)); Console.WriteLine("Erro genérico: {0}{1}Erro específico: {2}", fragmento.Status, Meio ambiente.Nova linha, fragmento.Status da operação("somepaththatdoesntexist" (algum caminho que não existe))); |
Nesse caso, como o caminho "somepaththatdoesntexist" não existia no documento, o erro específico retornado foi SubDocPathNotFound
. Há muitas combinações diferentes de erros, dependendo do tipo de construtor e da condição do erro.
Exemplos de LookupInBuilder
O LookUpInBuilder
suporta duas operações: buscar um valor por caminho e verificar a existência de um valor em um determinado caminho.
Obter:
Vamos procurar o proprietário
fragmento. Se eu passar "owner" como parâmetro de caminho para esse método...
1 2 3 4 5 6 7 8 9 |
público estático vazio GetExample(IBucket balde, string caminho, string id) { var construtor = balde.Pesquisa(id). Obter(caminho). Executar(); var fragmento = construtor.Conteúdo(caminho); Console.WriteLine(fragmento); } |
...a saída para o console seria:
1 2 3 4 5 |
{ "tipo": "servo", "name" (nome): "Don Knotts", "idade": 63 } |
Existir:
Também podemos verificar se um caminho existe. Se eu passar "owner" como o caminho para esse método...
1 2 3 4 5 6 7 8 9 |
público estático vazio ExistsExample(IBucket balde, string caminho, string id) { var construtor = balde.Pesquisa(id). Existe(caminho). Executar(); var encontrado = construtor.Conteúdo(caminho); Console.WriteLine(encontrado); } |
...o resultado é verdadeiro
porque o caminho proprietário
existe de fato no documento.
MutateInBuilder
O MutateInBuilder oferece vários métodos de suporte a mutações em valores escalares, dicionários e matrizes, além de suporte a operações de contador atômico.
Inserir:
Insert adiciona um valor a um dicionário e, opcionalmente, permite que o elemento que o contém (o próprio dicionário) seja adicionado.
1 2 3 4 5 6 7 8 9 |
público estático vazio InsertExample(IBucket balde, string id, string caminho, string valor) { var fragmento = balde.Mutação(id). Inserir(caminho, valor, verdadeiro). // falso é o padrão Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Se eu chamasse o método acima dessa forma:
1 |
InsertExample(balde, id,"attributes.hairLength", "curto"); |
Então, o dicionário de atributos do documento terá a seguinte aparência:
1 2 3 4 5 6 7 8 9 10 11 12 |
... "atributos": { "pulgas": verdadeiro, "cor": "branco", "eyeColor" (cor dos olhos): "marrom", "idade": 5, "sujo": verdadeiro, "sexo": "feminino", "hairLength" (comprimento do cabelo): "curto" } ... |
Observe que o Inserir
tem um parâmetro booleano opcional chamado createParents
. É falso por padrão. Se for verdadeiro, a API do subdocumento criará o caminho necessário para que o campo exista. Se for falso, a API do subdocumento só criará o campo se os pais do campo já existirem. No exemplo acima, o campo atributos
já existia.
No próximo exemplo, usarei um caminho com um campo pai (um novo atributo
) que ainda não exista.
1 |
InsertExample(balde, id, "anewattribute.withakey", "somevalue" (algum valor)); |
Isso criará o novo atributo chamado um novo atributo
no documento e adicione uma única chave chamada withakey
com um valor de algum valor
.
1 2 3 4 5 6 |
... "anewattribute": { "withakey": "somevalue" (algum valor) } ... |
Agora, se passarmos falso
para createParents
e o atributo pai não existisse, a mutação múltipla falharia com um status de resposta de nível superior de SubDocMultiPathFailure
e o erro específico seria SubDocPathNotFound
.
Upsert
Upsert adiciona ou substitui uma entrada existente no dicionário. O uso é exatamente o mesmo que Inserir
com exceção do nome do método que é Upsert
.
Remover
Remover
removerá um elemento em um determinado caminho.
1 2 3 4 5 6 7 8 9 |
público estático vazio RemoverExemplo(IBucket balde, string id, string caminho) { var fragmento = balde.Mutação(id). Remover(caminho). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Quando chamo esse método:
1 |
RemoverExemplo(balde, id, "owner.name"); |
Veja como o documento ficará depois:
1 2 3 4 5 6 7 |
... "proprietário": { "tipo": "servo", "idade": 63 }, ... |
Substituir
Replace trocará o valor do elemento em um determinado caminho, falhando se o caminho não existir:
1 2 3 4 5 6 7 8 9 |
público estático vazio ReplaceExample (SubstituirExemplo)(IBucket balde, string id, string caminho, objeto valor) { var fragmento = balde.Mutação(id). Substituir(caminho, valor). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Depois que eu chamar esse método:
1 |
ReplaceExample (SubstituirExemplo)(balde, id, "proprietário", novo { Amante de gatos=verdadeiro, CatName="celia"}); |
O documento agora terá um valor diferente para "proprietário":
1 2 3 4 5 6 7 |
... "proprietário": { "catLover": verdadeiro, "catName": "celia" }, ... |
ArrayAppend
O ArrayAppend adiciona um valor ao final de uma matriz, adicionando opcionalmente o elemento pai (o próprio elemento da matriz) se ele não existir.
1 2 3 4 5 6 7 8 9 |
público estático vazio ArrayAppendExample(IBucket balde, string id, string caminho, objeto valor) { var fragmento = balde.Mutação(id). ArrayAppend(caminho, valor, falso). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Depois desse método com o caminho "toys"...
1 |
ArrayAppendExample(balde, id, "brinquedos", "chinelo"); |
...o brinquedos
no documento terá o valor "slipper" no último ordinal:
1 2 3 4 5 6 7 8 9 |
... "brinquedos": [ "squeaker", "bola", "sapato", "chinelo" ], ... |
ArrayPrepender
O ArrayPrepend funciona da mesma forma que o ArrayAppend, exceto pelo fato de que ele adiciona um valor à variável frente de uma matriz.
1 2 3 4 5 6 7 8 9 |
público estático vazio ArrayAppendExample(IBucket balde, string id, string caminho, objeto valor) { var fragmento = balde.Mutação(id). ArrayAppend(caminho, valor, falso). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Chamando esse método com o caminho "toys"...
1 |
ArrayAppendExample(balde, id, "brinquedos", "chinelo"); |
O brinquedos
agora tem o valor "slipper" em sua matriz primeiro ordinal:
1 2 3 4 5 6 7 8 9 |
... "brinquedos": [ "chinelo", "squeaker", "bola", "sapato" ], ... |
ArrayInsert
O ArrayPrepend coloca um valor no início, o ArrayAppend o coloca no final. Para completar, você pode usar o ArrayInsert para colocar um valor em algum ponto intermediário (em um determinado índice).
1 2 3 4 5 6 7 8 9 |
público estático vazio ArrayInsertExample(IBucket balde, string id, string caminho, objeto valor) { var fragmento = balde.Mutação(id). ArrayInsert(caminho, valor). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
E, em seguida, chamar esse método com "toys[2]"...
1 |
ArrayInsertExample(balde, id, "toys[2]", "chinelo"); |
O brinquedos
A matriz agora tem o valor "slipper" em seu terceiro ordinal (índice 2):
1 2 3 4 5 6 7 |
"brinquedos": [ "squeaker", "bola", "chinelo", "sapato" ], |
ArrayAddUnique
O ArrayAddUnique insere um valor em uma matriz, mas falhará se esse valor já existir (ou seja, o valor deve ser exclusivo dentro da matriz).
1 2 3 4 5 6 7 8 9 |
público estático vazio ArrayAddUniqueExample(IBucket balde, string id, string caminho, objeto valor) { var fragmento = balde.Mutação(id). ArrayAddUnique(caminho, valor). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Quando eu chamo isso de "sapato"...
1 |
ArrayAddUniqueExample(balde, id, "brinquedos", "sapato"); |
...uma vez que o valor "shoe" já existe no documento original brinquedos
isso falhará com o status SubDocPathExists
.
Observe que esse método só permite a inserção de primitivos JSON: cadeias de caracteres, números e valores especiais para verdadeiro, falso ou nulo. Não há como comparar a exclusividade sem descer em cada objeto JSON e comparar os elementos item por item.
Balcão
Adiciona o delta especificado (alteração) a um valor existente, criando o elemento se ele não existir. O valor e o delta serão padronizados como 0. Se o delta for negativo, o valor do elemento será diminuído pelo delta fornecido.
Criarei um método que usa Balcão
:
1 2 3 4 5 6 7 8 9 |
público estático vazio CounterExample(IBucket balde, string id, string caminho, longo delta) { var fragmento = balde.Mutação(id). Balcão(caminho, delta). Executar(); var status = fragmento.Status da operação(caminho); Console.WriteLine(status); } |
Em seguida, chamarei o método duas vezes usando um 1 positivo e um 1 negativo como "deltas":
1 2 |
CounterExample(balde, id, "curtidas", 1); CounterExample(balde, id, "curtidas", -1); |
Após a primeira chamada, como o elemento não existe, ele será criado e, em seguida, definido como um (1). O documento agora terá a seguinte aparência:
1 2 3 4 |
... ], "curtidas": 1 } |
A segunda chamada passa um número negativo (-1), portanto, o contador para gostos
será decrementado para zero (0). O documento JSON agora terá a seguinte aparência:
1 2 3 4 |
... ], "curtidas": 0 } |
Conclusão
Desde a publicação no blog da visualização do desenvolvedor, o SDK do Couchbase .NET foi atualizado para a versão 2.3.2 (até o momento desta publicação no blog). Você pode conferir o trabalho que foi feito na seção Notas de versão da versão 2.3.2.
Como obter a versão 2.3.x
- Faça o download dos binários para o .NET SDK 2.3.x do nosso repositório (2.3.3 é a versão mais recente).
- Você pode encontrar o SDKs mais recentes no NuGet.
- O O código-fonte está disponível no Github
Notas finais
A API de subdocumentos permite que você seja mais granular em suas interações com os documentos. Você pode modificar e recuperar apenas as partes de que precisa.
Deixe um comentário abaixo, fale comigo no Twitterou envie um e-mail para mim (matthew.groves AT couchbase DOT com) se tiver alguma dúvida ou comentário.