Esta entrada del blog se basa en un entrada anterior del blog de Jeff Morris que cubría la API de subdocumentos cuando aún estaba en versión preliminar para desarrolladores. Ha habido algunos cambios en la API desde ese lanzamiento.
Con Servidor Couchbase 4.5 y el .NET SDK 2.3.xahora puede utilizar la función Subdocumento en su aplicación .NET.
En versiones anteriores de Couchbase, todas las mutaciones de documentos eran atómicas e involucraban a todo el documento. Si sólo quieres cambiar un único campo y luego hacer una actualización, todo el documento en el servidor Couchbase es copiado por la nueva revisión. El problema es que si el documento es grande o la red lenta (o ambos), entonces se desperdician muchos recursos enviando datos que no han sido modificados. Una solución mejor y más eficaz sería enviar sólo la parte del documento o el valor que se ha modificado. Esencialmente, eso es lo que se consigue con la API de subdocumento; cuando se actualiza un elemento o se elimina un elemento de un documento, sólo se envía por cable la ruta del fragmento que se va a mutar y sólo se modifica esa parte del documento.

La API admite varias operaciones diferentes, desde mutaciones en elementos anidados individuales (también conocidas como sub-) a modificaciones de matrices y diccionarios. También se admiten operaciones de contador, así como operaciones de recuperación de fragmentos JSON incrustados.
La API se expone a través de un fluido que le permite añadir múltiples operaciones y ejecutarlas contra el documento de forma atómica. Existen dos "constructores" diferentes: un constructor para operaciones de mutación y un constructor para lecturas o "búsquedas" (que también puede comprobar si un elemento existe en una ruta determinada).
Requisito previo: Servidor Couchbase 4.5
Para poder seguir los ejemplos que se presentan a continuación, deberá descargar e instalar Servidor Couchbase 4.5. Si nunca has instalado Couchbase Server, puedes ver mi vídeo sobre cómo instalar Couchbase Server en Windows. Es muy fácil, independientemente del sistema operativo que utilices.
Descripción general de la API de subdocumentos
Los siguientes ejemplos utilizarán un documento con un id de "cachorro" y empezarán con este aspecto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "tipo": "perro", "raza": "Pitbull/Chihuahua", "nombre": "Cachorro", "juguetes": [ "chillón", "pelota", "zapato" ], "propietario": { "tipo": "sirviente", "nombre": "Don Knotts", "edad": 63 }, "atributos": { "pulgas": verdadero, "color": "blanco", "eyeColor": "marrón", "edad": 5, "sucio": verdadero, "sexo": "mujer" } } |
Todos los Los ejemplos están disponibles en Github para que puedas clonar el proyecto y jugar con la API.
MutateInBuilder y LookupInBuilder
La API de subdocumentos ofrece dos nuevos tipos que utilizan un patrón constructor a través de una interfaz fluida para encadenar varias operaciones en un documento. Ambos objetos se crean llamando a MutateIn
o BúsquedaEn
en un CouchbaseBucket
e introduciendo la clave del documento con el que se está trabajando:
1 2 3 4 5 6 7 8 9 10 11 |
//Inicializar el cluster helper con la configuración por defecto - es decir, localhost ClusterHelper.Inicializar(); var cubo = ClusterHelper.GetBucket("por defecto"); //crear un constructor de mutaciones para el documento "thekey" var mutar = cubo.MutateIn("la llave"); //crear un constructor de búsqueda para el documento "thekey2" var buscar = cubo.BúsquedaEn("thekey2"); ClusterHelper.Cerrar(); |
Una vez que tenga el objeto constructor, puede encadenar una serie de operaciones para ejecutarlas contra el documento, por ejemplo:
1 2 3 4 5 |
var constructor = cubo.BúsquedaEn(id). Visite("tipo"). Visite("nombre"). Visite("propietario"). Existe("no encontrado"); |
A continuación, puede enviar todas las operaciones al servidor en un único lote:
1 |
var fragmento = constructor.Ejecute(); |
Puedes comprobar el resultado de una operación, utilizando OpStatus y una ruta. En este ejemplo, estoy usando la ruta "tipo"
:
1 2 3 4 5 |
si (fragmento.OpStatus("tipo") == ResponseStatus.Éxito) { cadena formato = "Ruta='{0}' Valor='{1}'"; Consola.WriteLine(formato, "tipo", fragmento.Contenido("tipo")); } |
Estos son algunos de los métodos y campos que encontrará en la aplicación IDocumentFragment
interfaz.
Nombre |
Descripción |
Contenido(...) |
Obtiene el contenido de una ruta o índice dados. |
Existe(...) |
Devuelve true si hay un resultado para una ruta o índice dados. |
Contar() |
El recuento de las operaciones en curso mantenidas por el constructor. |
OpStatus(...) |
En |
Estado |
En |
Éxito |
Verdadero si la operación múltiple completa tiene éxito. |
Además de estas propiedades o métodos, existen todas las demás propiedades heredadas de OperationResult
(que es la respuesta estándar de una operación clave/valor): Upsert, Remove, Replace, etc.
Tratamiento de errores
Al enviar varios mutacionesSi una de ellas falla, falla toda la solicitud multioperación. Esto permite una semántica transaccional de "todo o nada" al realizar mutaciones dentro de un mismo documento.
Al enviar varios búsquedasAlgunas operaciones pueden tener éxito y otras pueden fallar, intentando el servidor devolver tantos elementos como se le soliciten.
Si la(s) operación(es) ha(n) fallado, elEstado
contendrá una respuesta de error de nivel superior como SubDocMultiPathFailure
. Esto es una indicación de que debe profundizar en los resultados de la operación para obtener el error específico. Puede hacerlo iterando: llamando a la función OpStatus
y pasando el índice o la ruta:
1 2 3 4 5 6 7 8 9 10 11 |
var constructor = cubo.BúsquedaEn(id). Visite("tipo"). Visite("algún camino que no existe"). Visite("propietario"); var fragmento = constructor.Ejecute(); Consola.WriteLine("Error genérico: {0}{1}Error específico: {2}", fragmento.Estado, Medio ambiente.NewLine, fragmento.OpStatus(1)); Consola.WriteLine("Error genérico: {0}{1}Error específico: {2}", fragmento.Estado, Medio ambiente.NewLine, fragmento.OpStatus("algún camino que no existe")); |
En este caso, como la ruta "somepaththatdoesntexist" no existía dentro del documento, el error específico devuelto fue SubDocPathNotFound
. Hay muchas combinaciones diferentes de errores dependiendo del tipo de constructor y de la condición del error.
Ejemplos de LookupInBuilder
En LookUpInBuilder
admite dos operaciones: buscar un valor por ruta y comprobar la existencia de un valor en una ruta determinada.
Consíguelo:
Busquemos el propietario
fragmento. Si paso "owner" como parámetro de ruta a este método...
1 2 3 4 5 6 7 8 9 |
público estático void ObtenerEjemplo(IBucket cubo, cadena ruta, cadena id) { var constructor = cubo.BúsquedaEn(id). Visite(ruta). Ejecute(); var fragmento = constructor.Contenido(ruta); Consola.WriteLine(fragmento); } |
...la salida a consola sería:
1 2 3 4 5 |
{ "tipo": "sirviente", "nombre": "Don Knotts", "edad": 63 } |
Existe:
También podemos comprobar si existe una ruta. Si paso "owner" como la ruta a este método...
1 2 3 4 5 6 7 8 9 |
público estático void ExisteEjemplo(IBucket cubo, cadena ruta, cadena id) { var constructor = cubo.BúsquedaEn(id). Existe(ruta). Ejecute(); var encontrado = constructor.Contenido(ruta); Consola.WriteLine(encontrado); } |
...la salida es verdadero
porque el camino propietario
existe efectivamente en el documento.
MutateInBuilder
MutateInBuilder ofrece una serie de métodos que permiten realizar mutaciones en valores escalares, diccionarios y matrices, así como operaciones de contador atómico.
Insertar:
Insertar añade un valor a un diccionario, permitiendo opcionalmente que se añada el elemento que lo contiene (el propio diccionario).
1 2 3 4 5 6 7 8 9 |
público estático void InsertarEjemplo(IBucket cubo, cadena id, cadena ruta, cadena valor) { var fragmento = cubo.MutateIn(id). Inserte(ruta, valor, verdadero). // false es el valor por defecto Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Si llamara al método anterior así
1 |
InsertarEjemplo(cubo, id,"attributes.hairLength", "corto"); |
El diccionario de atributos del documento tendrá ahora este aspecto:
1 2 3 4 5 6 7 8 9 10 11 12 |
... "atributos": { "pulgas": verdadero, "color": "blanco", "eyeColor": "marrón", "edad": 5, "sucio": verdadero, "sexo": "mujer", "hairLength": "corto" } ... |
Tenga en cuenta que el Inserte
tiene un parámetro booleano opcional llamado crearPadres
. Es falso por defecto. Si es verdadero, la API de subdocumentos creará la ruta necesaria para que exista el campo. Si es falso, la API de subdocumentos sólo creará el campo si los padres del campo ya existen. En el ejemplo anterior, el campo atributos
ya existía.
En el siguiente ejemplo, utilizaré una ruta con un campo padre (unnuevoatributo
) que no exista ya.
1 |
InsertarEjemplo(cubo, id, "anewattribute.withakey", "algún valor"); |
Esto creará el nuevo atributo llamado unnuevoatributo
en el documento y añadir una única clave llamada conakey
con un valor de algún valor
.
1 2 3 4 5 6 |
... "unnuevoatributo": { "conakey": "algún valor" } ... |
Ahora, si pasamos falso
para crearPadres
y el atributo padre no existiera, la mutación múltiple fallaría con un estado de respuesta de nivel superior de SubDocMultiPathFailure
y el error específico sería SubDocPathNotFound
.
Upsert
Upsert añadirá o sustituirá una entrada del diccionario existente. El uso es exactamente el mismo que Inserte
con la excepción de que el nombre del método es Upsert
.
Eliminar
Eliminar
eliminará un elemento en una ruta determinada.
1 2 3 4 5 6 7 8 9 |
público estático void QuitarEjemplo(IBucket cubo, cadena id, cadena ruta) { var fragmento = cubo.MutateIn(id). Eliminar(ruta). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Cuando llamo a este método:
1 |
QuitarEjemplo(cubo, id, "nombre.propietario"); |
Este es el aspecto que tendrá el documento después:
1 2 3 4 5 6 7 |
... "propietario": { "tipo": "sirviente", "edad": 63 }, ... |
Sustituir
Reemplazar intercambiará el valor del elemento en una ruta dada, fallando si la ruta no existe:
1 2 3 4 5 6 7 8 9 |
público estático void ReemplazarEjemplo(IBucket cubo, cadena id, cadena ruta, objeto valor) { var fragmento = cubo.MutateIn(id). Sustituir(ruta, valor). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Después de llamar a este método:
1 |
ReemplazarEjemplo(cubo, id, "propietario", nuevo { Amante de los gatos=verdadero, CatName="celia"}); |
El documento tendrá ahora un valor diferente para "propietario":
1 2 3 4 5 6 7 |
... "propietario": { "catLover": verdadero, "catName": "celia" }, ... |
ArrayAppend
ArrayAppend añade un valor al final de un array, añadiendo opcionalmente el elemento padre (el propio elemento del array) si no existe.
1 2 3 4 5 6 7 8 9 |
público estático void ArrayAppendExample(IBucket cubo, cadena id, cadena ruta, objeto valor) { var fragmento = cubo.MutateIn(id). ArrayAppend(ruta, valor, falso). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Después de ese método con la ruta "juguetes"...
1 |
ArrayAppendExample(cubo, id, "juguetes", "zapatilla"); |
...el juguetes
del documento tendrá el valor "zapatilla" en el último ordinal:
1 2 3 4 5 6 7 8 9 |
... "juguetes": [ "chillón", "pelota", "zapato", "zapatilla" ], ... |
ArrayPrepend
ArrayPrepend funciona de la misma manera que ArrayAppend, excepto que añade un valor a la variable frente de una matriz.
1 2 3 4 5 6 7 8 9 |
público estático void ArrayAppendExample(IBucket cubo, cadena id, cadena ruta, objeto valor) { var fragmento = cubo.MutateIn(id). ArrayAppend(ruta, valor, falso). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Llamando a ese método con la ruta "juguetes"...
1 |
ArrayAppendExample(cubo, id, "juguetes", "zapatilla"); |
En juguetes
tiene ahora el valor "slipper" en su matriz primero ordinal:
1 2 3 4 5 6 7 8 9 |
... "juguetes": [ "zapatilla", "chillón", "pelota", "zapato" ], ... |
ArrayInsert
ArrayPrepend pone un valor al principio, ArrayAppend lo pone al final. Para redondear las cosas, puedes usar ArrayInsert para poner un valor en algún punto intermedio (en un índice dado).
1 2 3 4 5 6 7 8 9 |
público estático void ArrayInsertExample(IBucket cubo, cadena id, cadena ruta, objeto valor) { var fragmento = cubo.MutateIn(id). ArrayInsert(ruta, valor). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Y luego llamar a ese método con "juguetes[2]"...
1 |
ArrayInsertExample(cubo, id, "juguetes[2]", "zapatilla"); |
En juguetes
tiene ahora el valor "zapatilla" en su ordinal 3 (índice 2):
1 2 3 4 5 6 7 |
"juguetes": [ "chillón", "pelota", "zapatilla", "zapato" ], |
ArrayAddUnique
ArrayAddUnique insertará un valor en un array, pero fallará si ese valor ya existe (es decir, el valor debe ser único dentro del array).
1 2 3 4 5 6 7 8 9 |
público estático void ArrayAddUniqueExample(IBucket cubo, cadena id, cadena ruta, objeto valor) { var fragmento = cubo.MutateIn(id). ArrayAddUnique(ruta, valor). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Cuando llamo a eso con "zapato"...
1 |
ArrayAddUniqueExample(cubo, id, "juguetes", "zapato"); |
...puesto que el valor "shoe" ya existe en el documento original de juguetes
esto fallará con el estado SubDocPathExists
.
Tenga en cuenta que este método sólo permite insertar primitivas JSON: cadenas, números y valores especiales para true, false o null. No hay forma de comparar la unicidad sin descender a cada objeto JSON y comparar los elementos elemento por elemento.
Contador
Añade el delta (cambio) especificado a un valor existente, creando el elemento si no existe. Por defecto, tanto el valor como el delta serán 0. Si el delta es negativo, el valor del elemento se disminuirá en el delta dado.
Crearé un método que utilice Contador
:
1 2 3 4 5 6 7 8 9 |
público estático void Contraejemplo(IBucket cubo, cadena id, cadena ruta, largo delta) { var fragmento = cubo.MutateIn(id). Contador(ruta, delta). Ejecute(); var estado = fragmento.OpStatus(ruta); Consola.WriteLine(estado); } |
Entonces, llamaré al método dos veces usando un 1 positivo y un 1 negativo como los "deltas":
1 2 |
Contraejemplo(cubo, id, "me gusta", 1); Contraejemplo(cubo, id, "me gusta", -1); |
Después de la primera llamada, dado que el elemento no existe, se creará y se establecerá en uno (1). El documento tendrá ahora este aspecto:
1 2 3 4 |
... ], "me gusta": 1 } |
La segunda llamada pasa un uno negativo (-1), por lo que entonces el contador de le gusta
se reducirá a cero (0). El documento JSON tendrá ahora este aspecto:
1 2 3 4 |
... ], "me gusta": 0 } |
Conclusión
Desde la publicación en el blog de la vista previa para desarrolladores, el SDK .NET de Couchbase se ha actualizado a la versión 2.3.2 (en el momento de la publicación de este blog). Puedes echar un vistazo al trabajo que se ha hecho en la sección Notas de la versión 2.3.2.
Cómo conseguir la versión 2.3.x
- Descargar los binarios para .NET SDK 2.3.x de nuestro repositorio (2.3.3 es la última versión).
- Puede encontrar el últimos SDK en NuGet.
- En El código fuente está disponible en Github
Notas finales
La API de subdocumentos le ofrece la posibilidad de interactuar de forma más detallada con los documentos. Puede modificar y recuperar sólo las partes que necesite.
Deje un comentario a continuación, Háblame en Twittero envíeme un correo electrónico (matthew.groves AT couchbase DOT com) si tiene alguna pregunta o comentario.