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 |
{ "type": "dog", "breed": "Pitbull/Chihuahua", "name": "Puppy", "toys": [ "squeaker", "ball", "shoe" ], "owner": { "type": "servant", "name": "Don Knotts", "age": 63 }, "attributes": { "fleas": true, "color": "white", "eyeColor": "brown", "age": 5, "dirty": true, "sex": "female" } } |
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 |
//Initialize the cluster helper with the default settings - i.e. localhost ClusterHelper.Initialize(); var bucket = ClusterHelper.GetBucket("default"); //create a mutation builder for the document "thekey" var mutate = bucket.MutateIn("thekey"); //create a lookup builder for the document "thekey2" var lookup = bucket.LookupIn("thekey2"); ClusterHelper.Close(); |
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 builder = bucket.LookupIn(id). Get("type"). Get("name"). Get("owner"). Exists("notfound"); |
A continuación, puede enviar todas las operaciones al servidor en un único lote:
|
1 |
var fragment = builder.Execute(); |
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 |
if (fragment.OpStatus("type") == ResponseStatus.Success) { string format = "Path='{0}' Value='{1}'"; Console.WriteLine(format, "type", fragment.Content("type")); } |
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 builder = bucket.LookupIn(id). Get("type"). Get("somepaththatdoesntexist"). Get("owner"); var fragment = builder.Execute(); Console.WriteLine("Generic error: {0}{1}Specific Error: {2}", fragment.Status, Environment.NewLine, fragment.OpStatus(1)); Console.WriteLine("Generic error: {0}{1}Specific Error: {2}", fragment.Status, Environment.NewLine, fragment.OpStatus("somepaththatdoesntexist")); |
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 |
public static void GetExample(IBucket bucket, string path, string id) { var builder = bucket.LookupIn(id). Get(path). Execute(); var fragment = builder.Content(path); Console.WriteLine(fragment); } |
...la salida a consola sería:
|
1 2 3 4 5 |
{ "type": "servant", "name": "Don Knotts", "age": 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 |
public static void ExistsExample(IBucket bucket, string path, string id) { var builder = bucket.LookupIn(id). Exists(path). Execute(); var found = builder.Content(path); Console.WriteLine(found); } |
...la salida es verdaderoporque 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 |
public static void InsertExample(IBucket bucket, string id, string path, string value) { var fragment = bucket.MutateIn(id). Insert(path, value, true). // false is the default Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Si llamara al método anterior así
|
1 |
InsertExample(bucket, id,"attributes.hairLength", "short"); |
El diccionario de atributos del documento tendrá ahora este aspecto:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
... "attributes": { "fleas": true, "color": "white", "eyeColor": "brown", "age": 5, "dirty": true, "sex": "female", "hairLength": "short" } ... |
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 |
InsertExample(bucket, id, "anewattribute.withakey", "somevalue"); |
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 |
... "anewattribute": { "withakey": "somevalue" } ... |
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 |
public static void RemoveExample(IBucket bucket, string id, string path) { var fragment = bucket.MutateIn(id). Remove(path). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Cuando llamo a este método:
|
1 |
RemoveExample(bucket, id, "owner.name"); |
Este es el aspecto que tendrá el documento después:
|
1 2 3 4 5 6 7 |
... "owner": { "type": "servant", "age": 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 |
public static void ReplaceExample(IBucket bucket, string id, string path, object value) { var fragment = bucket.MutateIn(id). Replace(path, value). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Después de llamar a este método:
|
1 |
ReplaceExample(bucket, id, "owner", new { CatLover=true, CatName="celia"}); |
El documento tendrá ahora un valor diferente para "propietario":
|
1 2 3 4 5 6 7 |
... "owner": { "catLover": true, "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 |
public static void ArrayAppendExample(IBucket bucket, string id, string path, object value) { var fragment = bucket.MutateIn(id). ArrayAppend(path, value, false). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Después de ese método con la ruta "juguetes"...
|
1 |
ArrayAppendExample(bucket, id, "toys", "slipper"); |
...el juguetes del documento tendrá el valor "zapatilla" en el último ordinal:
|
1 2 3 4 5 6 7 8 9 |
... "toys": [ "squeaker", "ball", "shoe", "slipper" ], ... |
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 |
public static void ArrayAppendExample(IBucket bucket, string id, string path, object value) { var fragment = bucket.MutateIn(id). ArrayAppend(path, value, false). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Llamando a ese método con la ruta "juguetes"...
|
1 |
ArrayAppendExample(bucket, id, "toys", "slipper"); |
En juguetes tiene ahora el valor "slipper" en su matriz primero ordinal:
|
1 2 3 4 5 6 7 8 9 |
... "toys": [ "slipper", "squeaker", "ball", "shoe" ], ... |
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 |
public static void ArrayInsertExample(IBucket bucket, string id, string path, object value) { var fragment = bucket.MutateIn(id). ArrayInsert(path, value). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Y luego llamar a ese método con "juguetes[2]"...
|
1 |
ArrayInsertExample(bucket, id, "toys[2]", "slipper"); |
En juguetes tiene ahora el valor "zapatilla" en su ordinal 3 (índice 2):
|
1 2 3 4 5 6 7 |
"toys": [ "squeaker", "ball", "slipper", "shoe" ], |
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 |
public static void ArrayAddUniqueExample(IBucket bucket, string id, string path, object value) { var fragment = bucket.MutateIn(id). ArrayAddUnique(path, value). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Cuando llamo a eso con "zapato"...
|
1 |
ArrayAddUniqueExample(bucket, id, "toys", "shoe"); |
...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 |
public static void CounterExample(IBucket bucket, string id, string path, long delta) { var fragment = bucket.MutateIn(id). Counter(path, delta). Execute(); var status = fragment.OpStatus(path); Console.WriteLine(status); } |
Entonces, llamaré al método dos veces usando un 1 positivo y un 1 negativo como los "deltas":
|
1 2 |
CounterExample(bucket, id, "likes", 1); CounterExample(bucket, id, "likes", -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 |
... ], "likes": 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 |
... ], "likes": 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.