在所有 Firestore 文档上添加新字段或更改结构

考虑的集合。集合中的每个文档都有 和 as 字段。usersnameemail

{
  "users": {
    "uid1": {
      "name": "Alex Saveau",
      "email": "saveau.alexandre@gmail.com"
    },
    "uid2": { ... },
    "uid3": { ... }
  }
}

现在考虑一下,通过这种有效的Cloud Firestore数据库结构,我启动了我的第一个移动应用程序版本。然后,在某个时候,我意识到我想包括另一个字段,例如.last_login

在代码中,使用Java从Firestore DB读取所有用户文档将按如下方式完成

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));
                    }
                }
            }
        });

其中,类包含 now 和 。Usernameemaillast_login

由于新字段 () 未包含在存储在 DB 中的旧用户中,因此应用程序正在崩溃,因为新类需要一个由该方法返回的字段。Userlast_loginUserlast_loginnullget()

在不丢失新版本应用程序上的数据的情况下,包含在数据库的所有现有文档中的最佳做法是什么?我应该只运行一次代码段来执行此任务,还是有更好的方法来解决问题?last_loginUser


答案 1

您陷入了NOSQL数据库的空白:面向文档的数据库不能保证数据的结构完整性(如RDBMS所做的那样)

交易是:

  • RDBMS中,所有存储的数据在任何给定时间(在同一实例或集群中)都具有相同的结构。更改结构(ER 图)时,必须迁移所有现有记录的数据,这需要花费时间和精力。

    因此,可以针对当前版本的数据结构优化应用程序。

  • 面向文档的数据库中,每条记录都是一个独立的“页面”,具有自己独立的结构。如果更改结构,它仅适用于文档。因此,您无需迁移现有数据。

    因此,您的应用程序必须能够处理您在当前数据库中使用过的所有数据结构版本。

我不知道firebase的详细情况,但总的来说,你永远不会更新NOSQL数据库中的文档。您只需创建文档的新版本。因此,即使您更新了所有文档,您的应用程序也必须准备好处理“旧”数据结构...


答案 2

当我发布问题时,我写了一些例程来帮助自动化这个过程。我没有发布它们,因为这些有点简陋,我希望有一个优雅的基于Firestore的解决方案。由于这样的解决方案仍然不可用,因此以下是我编写的函数。

简而言之,我们有用于重命名字段,添加字段或删除字段的函数。要重命名字段,根据数据类型使用不同的函数。也许有人可以更好地概括这一点?以下功能如下:

  • add_field:在集合的所有文档中添加字段。
  • delete_field:删除集合的所有文档中的字段。
  • rename_*_field:重命名集合的所有文档中包含特定数据类型 (*) 的字段。在这里,我包括字符串,整数和日期的示例。

添加字段

public void add_field (final String key, final Object value, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {
                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Map<String, Object> new_map = new HashMap<>();
                            new_map.put(key, value);
                            batch.update(docRef, new_map);
                        }
                        batch.commit();
                    } else {
                        // ... "Error adding field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting documents -> " + e
                }
            });
}

删除字段

public void delete_field (final String key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Map<String, Object> delete_field = new HashMap<>();
                            delete_field.put(key, FieldValue.delete());
                            batch.update(docRef, delete_field);
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}

重命名字段

public void rename_string_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            String old_value = document.getString(old_key);

                            if (old_value != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices ->" + e
                }
            });
}

public void rename_integer_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            int old_value = document.getDouble(old_key).intValue();
                            Integer ov = old_value;
                            if (ov != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}

public void rename_date_field (final String old_key, final String new_key, final String collection_ref) {
    FirebaseFirestore.getInstance().collection(collection_ref).get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {

                        WriteBatch batch = db.batch();

                        for (DocumentSnapshot document : task.getResult()) {
                            DocumentReference docRef = document.getReference();
                            Date old_value = document.getDate(old_key);
                            if (old_value != null) {
                                Map<String, Object> new_map = new HashMap<>();
                                new_map.put(new_key, old_value);

                                Map<String, Object> delete_old = new HashMap<>();
                                delete_old.put(old_key, FieldValue.delete());

                                batch.update(docRef, new_map);
                                batch.update(docRef, delete_old);
                            }
                        }
                        // Commit the batch
                        batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                // ...
                            }
                        });

                    } else {
                        // ... "Error updating field -> " + task.getException()
                    }
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // ... "Failure getting notices -> " + e
                }
            });
}

推荐