web-dev-qa-db-fra.com

Ajouter un nouveau champ ou modifier la structure de tous les documents Firestore

Considérons une collection de users. Chaque document de la collection a name et email comme champs.

{
  "users": {
    "uid1": {
      "name": "Alex Saveau",
      "email": "[email protected]"
    },
    "uid2": { ... },
    "uid3": { ... }
  }
}

Considérez maintenant qu'avec cette structure de base de données Cloud Firestore qui fonctionne, je lance ma première version d'une application mobile. Puis, à un moment donné, je me rends compte que je veux inclure un autre champ tel que last_login.

Dans le code, la lecture de tous les documents utilisateurs de la base de données Firestore en utilisant Java se ferait comme

FirebaseFirestore.getInstance().collection("users").get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                    for (DocumentSnapshot document : task.getResult()) {
                        mUsers.add(document.toObject(User.class));
                    }
                }
            }
        });

où la classe User contient maintenant name, email et last_login.

Étant donné que le nouveau champ User (last_login) N'est pas inclus dans les anciens utilisateurs stockés dans la base de données, l'application se bloque car la nouvelle classe User attend un last_login Qui est retourné sous la forme null par la méthode get().

Quelle serait la meilleure pratique pour inclure last_login Dans tous les documents User existants de la base de données sans perdre leurs données sur une nouvelle version de l'application? Dois-je exécuter un extrait de code une seule fois pour effectuer cette tâche ou existe-t-il de meilleures approches du problème?

14
b-fg

Vous êtes tombé dans une lacune des bases de données NOSQL: les bases de données orientées documents ne garantissent pas l'intégrité structurelle des données (comme le font les SGBDR)

L'accord est:

  • dans un RDBMS toutes les données stockées ont la même structure à un moment donné (dans la même instance ou le même cluster). Lors du changement de structure (diagramme ER), vous devez migrer les données de tous les enregistrements existants, ce qui coûte du temps et de l'effort.

    En conséquence, votre application peut être optimisée pour la version actuelle de la structure de données.

  • dans une base de données orientée document chaque enregistrement est une "Page" indépendante avec sa propre structure indépendante. Si vous changez le texte, cela ne s'applique qu'aux documents nouvea. Vous n'avez donc pas besoin de migrer les données existantes.

    En conséquence, votre application doit être capable de gérer toutes les versions de votre structure de données que vous avez déjà utilisées dans votre base de données actuelle.

Je ne connais pas Firebase en détail, mais en général, vous n'avez jamais mise à jour un document dans une base de données NOSQL. Vous créez uniquement une nouvelle version du document. Donc, même si vous mettez à jour tous les documents, votre demande doit être préparée pour gérer la "vieille" structure de données ...

6
Timothy Truckle

Je suppose que last_login est un type de données primitif, peut-être un long pour contenir un horodatage. Un setter généré automatiquement ressemblerait à ceci:

private long last_login;

public void setLast_login(long last_login) {
    this.last_login = last_login;
}

Cela conduit à un plantage lorsque les anciens documents qui ne contiennent pas le champ sont récupérés en raison d'une affectation nulle à une variable d'un type de données primitif. Une façon de contourner cela est de modifier votre setter pour passer une variable de la classe wrapper équivalente - Long au lieu de long dans ce cas, et de mettre une vérification nulle dans le setter.

private long last_login;

public void setLast_login(Long last_login) {
    if(last_login != null) {
        this.last_login = last_login;
    }
}

Le coût pour éviter l'exception de pointeur nul est le surcoût boxe-unboxing.

1
a-hegde

Pour résoudre ce problème, vous devez mettre à jour chaque utilisateur pour avoir la nouvelle propriété et pour cela je vous recommande d'utiliser un Map. Si vous utilisez une classe de modèle lorsque vous créez les utilisateurs comme expliqué dans ma réponse post, pour tout mettre à jour utilisateurs, parcourez simplement la collection users AMD utilise le code suivant:

Map<String, Object> map = new HashMap<>();
map.put("timestamp", FieldValue.serverTimestamp());
userDocumentReference.set(map, SetOptions.merge());
1
Alex Mamo