Firestore .set方法每次生成新文档ID问题及解决方案咨询
问题核心
调用.set()时每次创建新文档,本质是因为MyObject.id为null——Firestore中doc(null)会自动生成一个随机文档ID,导致每次都是新建而非更新已有文档。
现有方案分析
预生成ID并存储:
确实存在文档ID重复存储的情况,但Firestore的文档ID属于路径信息,本身不占用文档存储空间(文档存储的是你写入的字段),所以这个"重复"的开销可以忽略。优点是读取文档时能直接从字段获取ID,无需额外处理,逻辑简单。添加后赋值ID:
缺陷很明显:无法保证所有MyObject实例都能同步拿到ID,尤其是离线操作或多实例场景下,容易出现后续操作仍用null ID创建新文档的问题,一致性难以保障,不推荐。用Map存储ID与对象映射:
增加了代码复杂度,需要维护Map和对象列表的同步(比如删除、更新操作时都要修改Map),在UI展示列表时还要额外处理Map转列表,开发成本更高,不推荐。
额外可行方案
1. 从文档快照直接获取ID赋值
修改MyObject的构造函数,新增一个从DocumentSnapshot初始化的方法,直接把快照的ID赋值给对象的id字段,而不是从文档内容中读取:
class MyObject { String? id; String name; String userId; // 原有的fromJson保留(用于手动构造或导入数据) MyObject.fromJson(Map<String, dynamic> json): id = json['id'], name = json['name'], userId = json['userId']; // 新增从快照初始化的构造函数 MyObject.fromSnapshot(DocumentSnapshot snapshot) : id = snapshot.id, name = snapshot.data()!['name'], userId = snapshot.data()!['userId']; }
这样从Firestore读取文档时,用MyObject.fromSnapshot(snapshot)初始化,得到的对象必然带有正确的文档ID,后续调用.set()就能精准更新。
2. 拆分新增与更新操作
在FirestoreService中分别实现新增和更新方法,明确区分操作逻辑:
class FirestoreService { // 新增文档:自动生成ID并赋值给对象 Future<String> addMyObject(MyObject myObject) async { final docRef = _firestore.collection('my_objects').doc(); myObject.id = docRef.id; // 如果不想在文档中存储ID,就把id从toJson中移除 await docRef.set(myObject.toJson()); return docRef.id; } // 更新文档:强制要求对象已有ID Future<void> updateMyObject(MyObject myObject) async { assert(myObject.id != null, "更新操作必须提供文档ID"); // 用update而非set,只修改变化的字段,更高效 await _firestore.collection('my_objects').doc(myObject.id).update(myObject.toJson()); } }
这种方式能避免误操作,同时update方法比set更节省带宽(只传输修改的字段)。
推荐方案
优先选择预生成ID+从快照读取ID的组合方案:
- 新增文档时,先生成ID并赋值给
MyObject.id,然后写入Firestore(可以选择把ID存在文档里,也可以不存——不存的话就完全依赖快照ID赋值)。 - 读取文档时,统一用
fromSnapshot构造函数初始化对象,确保所有从Firestore获取的实例都带有正确的ID。 - 更新时直接调用
.set()或.update(),因为ID已经存在,不会创建新文档。
这个方案兼顾了开发便利性和数据一致性,代码逻辑清晰,维护成本低。
内容的提问来源于stack exchange,提问作者BugsBuggy

