¿Cómo actualizar una "matriz de objetos" con Firestore?


104

Actualmente estoy probando Firestore, y estoy atascado en algo muy simple: "actualizar una matriz (también conocida como un subdocumento)".

Mi estructura de base de datos es super simple. Por ejemplo:

proprietary: "John Doe",
sharedWith:
  [
    {who: "first@test.com", when:timestamp},
    {who: "another@test.com", when:timestamp},
  ],

Estoy tratando (sin éxito) de insertar nuevos registros en una shareWithmatriz de objetos.

He intentado:

// With SET
firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
  { sharedWith: [{ who: "third@test.com", when: new Date() }] },
  { merge: true }
)

// With UPDATE
firebase.firestore()
.collection('proprietary')
.doc(docID)
.update({ sharedWith: [{ who: "third@test.com", when: new Date() }] })

Ninguno funciona. Estas consultas sobrescriben mi matriz.

La respuesta puede ser simple, pero no pude encontrarla ...

Respuestas:


71

Editar 13/08/2018 : ahora hay soporte para operaciones de arreglos nativos en Cloud Firestore. Vea la respuesta de Doug a continuación.


Actualmente, no hay forma de actualizar un solo elemento de matriz (o agregar / quitar un solo elemento) en Cloud Firestore.

Este código aquí:

firebase.firestore()
.collection('proprietary')
.doc(docID)
.set(
  { sharedWith: [{ who: "third@test.com", when: new Date() }] },
  { merge: true }
)

Esto dice que se establezca el documento de proprietary/docIDtal manera que sharedWith = [{ who: "third@test.com", when: new Date() }no afecte las propiedades del documento existente. Es muy similar a la update()llamada que proporcionó, sin embargo, la set()llamada creará el documento si no existe, mientras que la update()llamada fallará.

Entonces tienes dos opciones para lograr lo que deseas.

Opción 1: configurar toda la matriz

Llame set()con todo el contenido de la matriz, lo que requerirá leer primero los datos actuales de la base de datos. Si le preocupan las actualizaciones simultáneas, puede hacer todo esto en una transacción.

Opción 2: usar una subcolección

Puede hacer sharedWithuna subcolección del documento principal. Luego, agregar un solo elemento se vería así:

firebase.firestore()
  .collection('proprietary')
  .doc(docID)
  .collection('sharedWith')
  .add({ who: "third@test.com", when: new Date() })

Por supuesto, esto viene con nuevas limitaciones. No podrá consultar documentos en función de con quién se comparten, ni podrá obtener el documento y todos lossharedWith datos en una sola operación.


9
Esto es muy frustrante ... pero gracias por hacerme saber que no me estoy volviendo loco.
ItJustWerks

50
este es un gran inconveniente, Google tiene que solucionarlo lo antes posible.
Sajith Mantharath

3
La respuesta de @ DougGalante indica que esto se ha solucionado. Usa el arrayUnionmétodo.
quicklikerabbit

152

Firestore ahora tiene dos funciones que le permiten actualizar una matriz sin volver a escribir todo.

Enlace: https://firebase.google.com/docs/firestore/manage-data/add-data , específicamente https://firebase.google.com/docs/firestore/manage-data/add-data#update_elements_in_an_array

Actualizar elementos en una matriz

Si su documento contiene un campo de matriz, puede usar arrayUnion () y arrayRemove () para agregar y eliminar elementos. arrayUnion () agrega elementos a una matriz, pero solo elementos que no están presentes. arrayRemove () elimina todas las instancias de cada elemento dado.


56
¿Hay alguna forma de actualizar un índice específico de la matriz?
Artur Carvalho

1
¿Cómo usar esta función de actualización de matriz con "react-native-firebase"? (No puedo encontrar esto en los documentos oficiales de react-native-firebase)
kernelman

4
@ArturCarvalho No, la razón se explica en este video youtube.com/…
Adam

@ArturCarvalho, ¿ha encontrado alguna solución para actualizar un índice específico de la matriz?
Yogendra Patel

3
para quién necesita hacerlo en el cliente, use "import * as firebase from 'firebase / app';" luego "firebase.firestore.FieldValue.arrayUnion (NEW_ELEMENT)"
michelepatrassi

15

Puede usar una transacción ( https://firebase.google.com/docs/firestore/manage-data/transactions ) para obtener la matriz, presionarla y luego actualizar el documento:

    const booking = { some: "data" };
    const userRef = this.db.collection("users").doc(userId);

    this.db.runTransaction(transaction => {
        // This code may get re-run multiple times if there are conflicts.
        return transaction.get(userRef).then(doc => {
            if (!doc.data().bookings) {
                transaction.set({
                    bookings: [booking]
                });
            } else {
                const bookings = doc.data().bookings;
                bookings.push(booking);
                transaction.update(userRef, { bookings: bookings });
            }
        });
    }).then(function () {
        console.log("Transaction successfully committed!");
    }).catch(function (error) {
        console.log("Transaction failed: ", error);
    });

1
En la declaración if, debe cambiarlo a esto, porque le falta el documentReferencecomplemento userRefcomo se muestra: transaction.set(userRef, { bookings: [booking] });
Ilir Hushi

8

Aquí está el último ejemplo de la documentación de Firestore:

firebase.firestore.FieldValue. ArrayUnion

var washingtonRef = db.collection("cities").doc("DC");

// Atomically add a new region to the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});

// Atomically remove a region from the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});

@nifCody, esto de hecho agregará un nuevo elemento de cadena "mayor_virginia" a una matriz "regiones" existente. Lo probé con éxito, y definitivamente no agregué "objeto". Está sincronizado con la pregunta tal como se indica: "impulsar nuevos registros".
Veeresh Devireddy


3

Para aprovechar la respuesta de Sam Stern , también hay una tercera opción que me facilitó las cosas y es usar lo que Google llama un mapa, que es esencialmente un diccionario.

Creo que un diccionario es mucho mejor para el caso de uso que estás describiendo. Normalmente uso matrices para cosas que no se actualizan demasiado, por lo que son más o menos estáticas. Pero para las cosas que se escriben mucho, específicamente los valores que deben actualizarse para los campos que están vinculados a otra cosa en la base de datos, los diccionarios demuestran ser mucho más fáciles de mantener y trabajar.

Entonces, para su caso específico, la estructura de la base de datos se vería así:

proprietary: "John Doe"
sharedWith:{
  whoEmail1: {when: timestamp},
  whoEmail2: {when: timestamp}
}

Esto le permitirá hacer lo siguiente:

var whoEmail = 'first@test.com';

var sharedObject = {};
sharedObject['sharedWith.' + whoEmail + '.when'] = new Date();
sharedObject['merge'] = true;

firebase.firestore()
.collection('proprietary')
.doc(docID)
.update(sharedObject);

La razón para definir el objeto como una variable es que usarlo 'sharedWith.' + whoEmail + '.when'directamente en el método set resultará en un error, al menos cuando lo use en una función de nube de Node.js.


2

Aparte de las respuestas mencionadas anteriormente. Esto lo hará. Usando Angular 5 y AngularFire2. o use firebase.firestore () en lugar de this.afs

  // say you have have the following object and 
  // database structure as you mentioned in your post
  data = { who: "third@test.com", when: new Date() };

  ...othercode


  addSharedWith(data) {

    const postDocRef = this.afs.collection('posts').doc('docID');

    postDocRef.subscribe( post => {

      // Grab the existing sharedWith Array
      // If post.sharedWith doesn`t exsit initiated with empty array
      const foo = { 'sharedWith' : post.sharedWith || []};

      // Grab the existing sharedWith Array
      foo['sharedWith'].push(data);

      // pass updated to fireStore
      postsDocRef.update(foo);
      // using .set() will overwrite everything
      // .update will only update existing values, 
      // so we initiated sharedWith with empty array
    });
 }  

1

Considere a John Doe como un documento en lugar de una colección

Dale una colección de cosas y cosasSharedWithOthers

Luego, puede mapear y consultar las cosas compartidas de John Doe en esa colección paralela thingsSharedWithOthers.

proprietary: "John Doe"(a document)

things(collection of John's things documents)

thingsSharedWithOthers(collection of John's things being shared with others):
[thingId]:
    {who: "first@test.com", when:timestamp}
    {who: "another@test.com", when:timestamp}

then set thingsSharedWithOthers

firebase.firestore()
.collection('thingsSharedWithOthers')
.set(
{ [thingId]:{ who: "third@test.com", when: new Date() } },
{ merge: true }
)

0

Si alguien está buscando una solución sdk de Java Firestore para agregar elementos en el campo de matriz:

List<String> list = java.util.Arrays.asList("A", "B");
Object[] fieldsToUpdate = list.toArray();
DocumentReference docRef = getCollection().document("docId");
docRef.update(fieldName, FieldValue.arrayUnion(fieldsToUpdate));

Para eliminar elementos del usuario de la matriz: FieldValue.arrayRemove()



-1

Así es como lo hice funcionar. Espero que les ayude a todos, ya que lo veo como una solución mucho mejor. Aquí quiero cambiar el estado de la tarea de abierta (cerrar = falso) a cerrar (cerrar = verdadero), manteniendo todo lo demás igual en el objeto.

closeTask(arrayIndex) {
        let tasks = this.lead.activityTasks;

        const updatedTask = {
          name: tasks[arrayIndex].name,
          start: tasks[arrayIndex].start,
          dueDate:tasks[arrayIndex].dueDate,
          close: true, // This is what I am changing.
        };
        tasks[arrayIndex] = updatedTask;

        const data = {
          activityTasks: tasks
        };

        this.leadService.updateLeadData(data, this.lead.id);
    }

y aquí está el servicio que realmente lo actualiza

 public updateLeadData(updateData, leadId) {
    const leadRef: AngularFirestoreDocument<LeadModel> = this.afs.doc(
      `leads/${leadId}`);

return leadRef.update(updateData);
}

Hola. Tenga en cuenta que este enfoque tiene muchos problemas y riesgos de pérdida / corrupción / inflexibilidad de datos debido a un uso desafortunado de matrices y una "actualización" no incremental. No debe almacenar las tareas en una matriz. En su lugar, téngalos en una colección.
KarolDepka

Lo siento, tuve que votar en contra porque tiene malas prácticas.
KarolDepka
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.