Google Firestore - 如何在一次往返中通过多个 ID 获取多个文档?
Google Firestore - How to get several documents by multiple ids in one round-trip?
我想知道是否可以在一次往返(网络调用)到 Firestore 数据库的过程中通过 ID 列表获取多个文档。
不,目前无法使用 Cloud Firestore SDK 对多个读取请求进行批处理,因此无法保证您可以一次读取所有数据。
然而,正如 Frank van Puffelen 在上面的评论中所说,这并不意味着获取 3 个文档的速度是获取一个文档的 3 倍。在这里得出结论之前,最好进行自己的测量。
目前这在 Firestore 中似乎是不可能的。我不明白为什么亚历山大的答案被接受,他提出的解决方案只是 returns "users" 集合中的所有文档。
根据您需要做的事情,您应该考虑复制您需要显示的相关数据,并且只在需要时请求完整的文档。
如果你在节点内:
https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L978
/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
* console.log(`First document: ${JSON.stringify(docs[0])}`);
* console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/
这是服务端专用SDK
更新:
“Cloud Firestore [客户端 sdk] 现在支持 IN 查询!”
https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html
myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])
当然最好的方法是在 Cloud Functions 中实现 Firestore 的实际查询?然后只有一个从客户端到 Firebase 的往返调用,这似乎是你所要求的。
无论如何,您确实希望像服务器端一样保留所有数据访问逻辑。
内部可能会有相同数量的对 Firebase 本身的调用,但它们都将跨越 Google 的 super-fast 互连,而不是外部网络,并与流水线相结合正如 Frank van Puffelen 所解释的那样,您应该从这种方法中获得出色的性能。
您可以使用这样的函数:
function getById (path, ids) {
return firestore.getAll(
[].concat(ids).map(id => firestore.doc(`${path}/${id}`))
)
}
可以用一个ID调用:
getById('collection', 'some_id')
或 ID 数组:
getById('collection', ['some_id', 'some_other_id'])
你能做的最好的事情是 而不是 使用 Promise.all
作为你的客户然后必须等待 .all
继续之前的阅读。
迭代读取并让它们独立解决。在客户端,这可能归结为 UI 让几个进度加载器图像独立解析为值。但是,这比冻结整个客户端要好,直到 .all
读取解决。
因此,立即将所有同步结果转储到视图,然后让异步结果在它们解析时单独进入。这似乎是微不足道的区别,但如果您的客户的 Internet 连接不佳(就像我目前在这家咖啡店的情况一样),将整个客户体验冻结几秒钟可能会导致 'this app sucks' 体验。
实际上你会像这样使用firestore.getAll
async getUsers({userIds}) {
const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
const users = await this.firestore.getAll(...refs)
console.log(users.map(doc => doc.data()))
}
或使用 promise 语法
getUsers({userIds}) {
const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}
以下是使用 Android SDK 在 Kotlin 中执行类似操作的方法。
不一定在一次往返中,但它确实有效地对结果进行了分组并避免了许多嵌套回调。
val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }
Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
//Do what you need to with the document list
}
请注意,获取特定文档比获取所有文档并过滤结果要好得多。这是因为 Firestore 会向您收取查询结果集的费用。
希望对你有帮助,对我有用。
getCartGoodsData(id) {
const goodsIDs: string[] = [];
return new Promise((resolve) => {
this.fs.firestore.collection(`users/${id}/cart`).get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
goodsIDs.push(doc.id);
});
const getDocs = goodsIDs.map((id: string) => {
return this.fs.firestore.collection('goods').doc(id).get()
.then((docData) => {
return docData.data();
});
});
Promise.all(getDocs).then((goods: Goods[]) => {
resolve(goods);
});
});
});
}
他们刚刚宣布了此功能,https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html。
现在您可以使用类似的查询,但请注意输入大小不能大于 10。
userCollection.where('uid', 'in', ["1231","222","2131"])
如果你正在使用flutter,你可以这样做:
Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();
这将 return 包含 List<DocumentSnapshot>
的 Future,您可以根据需要对其进行迭代。
对于那些想使用 Angular 来做的人,这里有一个例子:
首先需要导入一些库:(必须预先安装)
import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'
集合的一些配置:
yourCollection: AngularFirestoreCollection;
constructor(
private _db : AngularFirestore,
) {
// this is your firestore collection
this.yourCollection = this._db.collection('collectionName');
}
下面是查询的方法:('products_id' 是一个 ID 数组)
getProducts(products_ids) {
var queryId = firebase.firestore.FieldPath.documentId();
this.yourCollection.ref.where(queryId, 'in', products_ids).get()
.then(({ docs }) => {
console.log(docs.map(doc => doc.data()))
})
}
是的,这是可能的。适用于 Firestore 的 .NET SDK 中的示例:
/*List of document references, for example:
FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
List<DocumentReference> docRefList = YOUR_DOCUMENT_REFERENCE_LIST;
// Required fields of documents, not necessary while fetching entire documents
FieldMask fieldMask = new FieldMask(FIELD-1, FIELD-2, ...);
// With field mask
List<DocumentSnapshot> documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
// Without field mask
List<DocumentSnapshot>documentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);
.NET 中的文档:
您可以使用文档 ID(最多十个)执行 IN 查询:
import {
query,
collection,
where,
getDocs,
documentId,
} from 'firebase/firestore';
export async function fetchAccounts(
ids: string[]
) {
// use lodash _.chunk, for example
const result = await Promise.all(
chunk(ids, 10).map(async (chunkIds) => {
const accounts = await getDocs(
query(
collection(firestore, 'accounts'),
where(documentId(), 'in', chunkIds)
));
return accounts.docs.filter(doc => doc.exists()).map(doc => doc.data());
})
);
return result.flat(1);
}
对于一些陷入同样问题的人
这是一个示例代码:
List<String> documentsIds = {your document ids};
FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
YourClass object = document.toObject(YourClass.class);
// add to your custom list
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
e.printStackTrace();
}
});
使用 Firebase 版本 9(2021 年 12 月更新):
您可以使用"documentId()" 和 一次往返 获取多个文档“in” with “where” 子句:
import {
query,
collection,
where,
documentId,
getDocs
} from "firebase/firestore";
const q = query(
collection(db, "products"),
where(documentId(), "in",
[
"8AVJvG81kDtb9l6BwfCa",
"XOHS5e3KY9XOSV7YYMw2",
"Y2gkHe86tmR4nC5PTzAx"
]
),
);
const productsDocsSnap = await getDocs(q);
productsDocsSnap.forEach((doc) => {
console.log(doc.data()); // "doc1", "doc2" and "doc3"
});
我想知道是否可以在一次往返(网络调用)到 Firestore 数据库的过程中通过 ID 列表获取多个文档。
不,目前无法使用 Cloud Firestore SDK 对多个读取请求进行批处理,因此无法保证您可以一次读取所有数据。
然而,正如 Frank van Puffelen 在上面的评论中所说,这并不意味着获取 3 个文档的速度是获取一个文档的 3 倍。在这里得出结论之前,最好进行自己的测量。
目前这在 Firestore 中似乎是不可能的。我不明白为什么亚历山大的答案被接受,他提出的解决方案只是 returns "users" 集合中的所有文档。
根据您需要做的事情,您应该考虑复制您需要显示的相关数据,并且只在需要时请求完整的文档。
如果你在节点内:
https://github.com/googleapis/nodejs-firestore/blob/master/dev/src/index.ts#L978
/**
* Retrieves multiple documents from Firestore.
*
* @param {...DocumentReference} documents - The document references
* to receive.
* @returns {Promise<Array.<DocumentSnapshot>>} A Promise that
* contains an array with the resulting document snapshots.
*
* @example
* let documentRef1 = firestore.doc('col/doc1');
* let documentRef2 = firestore.doc('col/doc2');
*
* firestore.getAll(documentRef1, documentRef2).then(docs => {
* console.log(`First document: ${JSON.stringify(docs[0])}`);
* console.log(`Second document: ${JSON.stringify(docs[1])}`);
* });
*/
这是服务端专用SDK
更新: “Cloud Firestore [客户端 sdk] 现在支持 IN 查询!”
https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html
myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"])
当然最好的方法是在 Cloud Functions 中实现 Firestore 的实际查询?然后只有一个从客户端到 Firebase 的往返调用,这似乎是你所要求的。
无论如何,您确实希望像服务器端一样保留所有数据访问逻辑。
内部可能会有相同数量的对 Firebase 本身的调用,但它们都将跨越 Google 的 super-fast 互连,而不是外部网络,并与流水线相结合正如 Frank van Puffelen 所解释的那样,您应该从这种方法中获得出色的性能。
您可以使用这样的函数:
function getById (path, ids) {
return firestore.getAll(
[].concat(ids).map(id => firestore.doc(`${path}/${id}`))
)
}
可以用一个ID调用:
getById('collection', 'some_id')
或 ID 数组:
getById('collection', ['some_id', 'some_other_id'])
你能做的最好的事情是 而不是 使用 Promise.all
作为你的客户然后必须等待 .all
继续之前的阅读。
迭代读取并让它们独立解决。在客户端,这可能归结为 UI 让几个进度加载器图像独立解析为值。但是,这比冻结整个客户端要好,直到 .all
读取解决。
因此,立即将所有同步结果转储到视图,然后让异步结果在它们解析时单独进入。这似乎是微不足道的区别,但如果您的客户的 Internet 连接不佳(就像我目前在这家咖啡店的情况一样),将整个客户体验冻结几秒钟可能会导致 'this app sucks' 体验。
实际上你会像这样使用firestore.getAll
async getUsers({userIds}) {
const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
const users = await this.firestore.getAll(...refs)
console.log(users.map(doc => doc.data()))
}
或使用 promise 语法
getUsers({userIds}) {
const refs = userIds.map(id => this.firestore.doc(`users/${id}`))
this.firestore.getAll(...refs).then(users => console.log(users.map(doc => doc.data())))
}
以下是使用 Android SDK 在 Kotlin 中执行类似操作的方法。
不一定在一次往返中,但它确实有效地对结果进行了分组并避免了许多嵌套回调。
val userIds = listOf("123", "456")
val userTasks = userIds.map { firestore.document("users/${it!!}").get() }
Tasks.whenAllSuccess<DocumentSnapshot>(userTasks).addOnSuccessListener { documentList ->
//Do what you need to with the document list
}
请注意,获取特定文档比获取所有文档并过滤结果要好得多。这是因为 Firestore 会向您收取查询结果集的费用。
希望对你有帮助,对我有用。
getCartGoodsData(id) {
const goodsIDs: string[] = [];
return new Promise((resolve) => {
this.fs.firestore.collection(`users/${id}/cart`).get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
goodsIDs.push(doc.id);
});
const getDocs = goodsIDs.map((id: string) => {
return this.fs.firestore.collection('goods').doc(id).get()
.then((docData) => {
return docData.data();
});
});
Promise.all(getDocs).then((goods: Goods[]) => {
resolve(goods);
});
});
});
}
他们刚刚宣布了此功能,https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html。
现在您可以使用类似的查询,但请注意输入大小不能大于 10。
userCollection.where('uid', 'in', ["1231","222","2131"])
如果你正在使用flutter,你可以这样做:
Firestore.instance.collection('your collection name').where(FieldPath.documentId, whereIn:[list containing multiple document IDs]).getDocuments();
这将 return 包含 List<DocumentSnapshot>
的 Future,您可以根据需要对其进行迭代。
对于那些想使用 Angular 来做的人,这里有一个例子:
首先需要导入一些库:(必须预先安装)
import * as firebase from 'firebase/app'
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'
集合的一些配置:
yourCollection: AngularFirestoreCollection;
constructor(
private _db : AngularFirestore,
) {
// this is your firestore collection
this.yourCollection = this._db.collection('collectionName');
}
下面是查询的方法:('products_id' 是一个 ID 数组)
getProducts(products_ids) {
var queryId = firebase.firestore.FieldPath.documentId();
this.yourCollection.ref.where(queryId, 'in', products_ids).get()
.then(({ docs }) => {
console.log(docs.map(doc => doc.data()))
})
}
是的,这是可能的。适用于 Firestore 的 .NET SDK 中的示例:
/*List of document references, for example:
FirestoreDb.Collection(ROOT_LEVEL_COLLECTION).Document(DOCUMENT_ID);*/
List<DocumentReference> docRefList = YOUR_DOCUMENT_REFERENCE_LIST;
// Required fields of documents, not necessary while fetching entire documents
FieldMask fieldMask = new FieldMask(FIELD-1, FIELD-2, ...);
// With field mask
List<DocumentSnapshot> documentSnapshotsMasked = await FirestoreDb.GetAllSnapshotsAsync(docRefList, fieldMask);
// Without field mask
List<DocumentSnapshot>documentSnapshots = await FirestoreDb.GetAllSnapshotsAsync(docRefList);
.NET 中的文档:
您可以使用文档 ID(最多十个)执行 IN 查询:
import {
query,
collection,
where,
getDocs,
documentId,
} from 'firebase/firestore';
export async function fetchAccounts(
ids: string[]
) {
// use lodash _.chunk, for example
const result = await Promise.all(
chunk(ids, 10).map(async (chunkIds) => {
const accounts = await getDocs(
query(
collection(firestore, 'accounts'),
where(documentId(), 'in', chunkIds)
));
return accounts.docs.filter(doc => doc.exists()).map(doc => doc.data());
})
);
return result.flat(1);
}
对于一些陷入同样问题的人 这是一个示例代码:
List<String> documentsIds = {your document ids};
FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
YourClass object = document.toObject(YourClass.class);
// add to your custom list
}
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
e.printStackTrace();
}
});
使用 Firebase 版本 9(2021 年 12 月更新):
您可以使用"documentId()" 和 一次往返 获取多个文档“in” with “where” 子句:
import {
query,
collection,
where,
documentId,
getDocs
} from "firebase/firestore";
const q = query(
collection(db, "products"),
where(documentId(), "in",
[
"8AVJvG81kDtb9l6BwfCa",
"XOHS5e3KY9XOSV7YYMw2",
"Y2gkHe86tmR4nC5PTzAx"
]
),
);
const productsDocsSnap = await getDocs(q);
productsDocsSnap.forEach((doc) => {
console.log(doc.data()); // "doc1", "doc2" and "doc3"
});