先前查询最后一个文档后的 Firestore 查询?

Firestore query after previous queries last document?

我首先进行查询并获得前 30 个“最喜欢”的帖子并将其与该查询的最后一个文档一起发送给客户。

接下来,当客户端加载更多时,我(尝试)查询接下来的 30 个文档。我发回最后一个文档并尝试查询它,但每次尝试都失败或加载与以前相同的帖子。

exports.loadTrendingPosts = functions.https.onCall(async(data, context) => {
   var startData = data.startAt; //either null(first load) or previous last document
   var currentTime = admin.firestore.Timestamp.now();
   var lastDocument;
   var monthYearCompare = '202001';

   if(startData !== null && startData !== undefined){

    //produces the error "Cannot read property 'firestore' of undefined"
    //startData = new admin.firestore.QueryDocumentSnapshot(data.startData);

    //produces the error "Cannot read property 'firestore' of undefined"
    //startData = new admin.firestore.DocumentSnapshot(data.startData);

    var postSnap = await db.collection('posts')
        .where('nextTwelveMonths', 'array-contains', monthYearCompare)
        .orderBy('likes', 'desc')
        .startAfter(startData)
        .limit(30)
        .get();
   }
   else{ //initial load
      postSnap = await db.collection('posts').where('nextTwelveMonths', 'array-contains', monthYearCompare).orderBy('likes', 'desc').limit(30).get();
      lastDocument = postSnap.docs[postSnap.docs.length - 1]

   }
   
  return [postsSnap, lastDocument];
});

startData 返回到函数 JSON/maps。 我尝试过的事情:

-如果我尝试在 startData 之后开始查询而不做任何更改,我不会收到任何错误,但我会得到与初始查询相同的 30 个帖子。

-如果没有更改我尝试使用 startData.data().likes 我得到错误 startData.data is not a function

-如果我尝试将 JSON/maps 转换为 DocumentSnapshotQueryDocumentSnapshot 以使我能够使用 .data(),我会收到错误 Cannot read property 'firestore' of undefined

我无法添加 where('likes', '<', lastMinLikes),因为很多帖子的点赞数都相同。

关于如何在这种情况下加载接下来的 30 个文档有什么想法吗?

编辑:这是从客户端(通过 Flutter)发回文档时 startData 的样子: (当我 startAt 使用它时,我得到相同的初始 30 个文档 ~ 这没有区别)。

编辑:想出了一个解决方案,请参阅下面的答案。

{_createTime: {_nanoseconds: 412572000, _seconds: 1613004211}, _fieldsProto: {fileName: {stringValue: image_picker_CDEC8665-93ED-4453-A021-2E2F2B7C86F7-3920-000002602CF620C2.jpg, valueType: stringValue}, comments: {valueType: arrayValue, arrayValue: {values: []}}, nextTwentyFour: {valueType: arrayValue, arrayValue: {values: [{stringValue: 2021011100, valueType: stringValue}, {stringValue: 2021011101, valueType: stringValue}, {stringValue: 2021011102, valueType: stringValue}, {stringValue: 2021011103, valueType: stringValue}, {stringValue: 2021011104, valueType: stringValue}, {stringValue: 2021011105, valueType: stringValue}, {stringValue: 2021011106, valueType: stringValue}, {stringValue: 2021011107, valueType: stringValue}, {stringValue: 2021011108, valueType: stringValue}, {stringValue: 2021011109, valueType: stringValue}, {stringValue: 2021011110, valueType: stringValue}, {stringValue: 2021011111, valueType: stringValue}, {stringValue: 2021011112, valueType: stringValue}, {stringValue: 2021011113, valueType: stringValue}, {stringValue: 2021011114, valueType: stringValue}, {stringValue: 2021011115, valueType: stringValue}, {stringValue: 2021011116, valueType: stringValue}, {stringValue: 2021011117, valueType: stringValue}, {stringValue: 2021011118, valueType: stringValue}, {stringValue: 2021011119, valueType: stringValue}, {stringValue: 2021011120, valueType: stringValue}, {stringValue: 2021011121, valueType: stringValue}, {stringValue: 2021011122, valueType: stringValue}, {stringValue: 2021011123, valueType: stringValue}]}}, nextTwelveMonths: {valueType: arrayValue, arrayValue: {values: [{stringValue: 202102, valueType: stringValue}, {stringValue: 202103, valueType: stringValue}, {stringValue: 202104, valueType: stringValue}, {stringValue: 202105, valueType: stringValue}, {stringValue: 202106, valueType: stringValue}, {stringValue: 202107, valueType: stringValue}, {stringValue: 202108, valueType: stringValue}, {stringValue: 202109, valueType: stringValue}, {stringValue: 202110, valueType: stringValue}, {stringValue: 202111, valueType: stringValue}, {stringValue: 202200, valueType: stringValue}, {stringValue: 202201, valueType: stringValue}]}}, campaignID: {stringValue: kLgdsaYbuqdf1w5UXRp9, valueType: stringValue}, dealID: {stringValue: 0b8ImRRtnKcsGWrTJ5rI, valueType: stringValue}, nextThirtyOneDays: {valueType: arrayValue, arrayValue: {values: [{stringValue: 20210111, valueType: stringValue}, {stringValue: 20210112, valueType: stringValue}, {stringValue: 20210113, valueType: stringValue}, {stringValue: 20210114, valueType: stringValue}, {stringValue: 20210115, valueType: stringValue}, {stringValue: 20210116, valueType: stringValue}, {stringValue: 20210117, valueType: stringValue}, {stringValue: 20210118, valueType: stringValue}, {stringValue: 20210119, valueType: stringValue}, {stringValue: 20210120, valueType: stringValue}, {stringValue: 20210121, valueType: stringValue}, {stringValue: 20210122, valueType: stringValue}, {stringValue: 20210123, valueType: stringValue}, {stringValue: 20210124, valueType: stringValue}, {stringValue: 20210125, valueType: stringValue}, {stringValue: 20210126, valueType: stringValue}, {stringValue: 20210127, valueType: stringValue}, {stringValue: 20210128, valueType: stringValue}, {stringValue: 20210201, valueType: stringValue}, {stringValue: 20210202, valueType: stringValue}, {stringValue: 20210203, valueType: stringValue}, {stringValue: 20210204, valueType: stringValue}, {stringValue: 20210205, valueType: stringValue}, {stringValue: 20210206, valueType: stringValue}, {stringValue: 20210207, valueType: stringValue}, {stringValue: 20210208, valueType: stringValue}, {stringValue: 20210209, valueType: stringValue}, {stringValue: 20210210, valueType: stringValue}, {stringValue: 20210211, valueType: stringValue}, {stringValue: 20210212, valueType: stringValue}, {stringValue: 20210213, valueType: stringValue}]}}, caption: {stringValue: Nails all done , valueType: stringValue}, userCreatorsName: {stringValue: John Onewick, valueType: stringValue}, commentCount: {valueType: integerValue, integerValue: 0}, usersCreatorsID: {stringValue: xaLbvMoFlzQgU0w1UeZqvWiVHy92, valueType: stringValue}, size: {valueType: integerValue, integerValue: 1}, usersName: {stringValue: Four Loko, valueType: stringValue}, usersID: {stringValue: C1oiRCuj6OQOF0aXgaAlzSKOe2y2, valueType: stringValue}, campaignName: {stringValue: Epc Gamer Moments, valueType: stringValue}, nextSevenDays: {valueType: arrayValue, arrayValue: {values: [{stringValue: 20210111, valueType: stringValue}, {stringValue: 20210112, valueType: stringValue}, {stringValue: 20210113, valueType: stringValue}, {stringValue: 20210114, valueType: stringValue}, {stringValue: 20210115, valueType: stringValue}, {stringValue: 20210116, valueType: stringValue}, {stringValue: 20210117, valueType: stringValue}]}}, fileType: {valueType: integerValue, integerValue: 0}, timestamp: {timestampValue: {seconds: 1613004211, nanos: 278000000}, valueType: timestampValue}, likes: {valueType: integerValue, integerValue: 0}}, _ref: {_firestore: {_clientPool: {terminateDeferred: {resolve: {}, reject: {}, promise: {}}, clientFactory: {}, clientDestructor: {}, maxIdleClients: 1, concurrentOperationLimit: 100, activeClients: {}, failedClients: {}, terminated: false}, _settings: {firebaseVersion: 9.4.2, libName: gccl, projectId: removeForStackOF-XXX, libVersion: 4.8.1 fire/9.4.2}, _settingsFrozen: true, _serializer: {allowUndefined: false, createReference: {}, createInteger: {}}, bulkWritersCount: 0, registeredListenersCount: 0, _backoffSettings: {backoffFactor: 1.3, maxDelayMs: 60000, initialDelayMs: 100}, _projectId: removeForStackOF-XXX}, _converter: {toFirestore: {}, fromFirestore: {}}, _path: {databaseId: (default), projectId: removeForStackOF-XXX, segments: [posts, 0b8ImRRtnKcsGWrTJ5rI]}}, _serializer: {allowUndefined: false, createReference: {}, createInteger: {}}, _readTime: {_nanoseconds: 522978000, _seconds: 1613095062}, _updateTime: {_nanoseconds: 412572000, _seconds: 1613004211}}

我怀疑用于 startAfter 的来自 startData 的数据格式不正确。我使用以下实体创建了示例集合“0”。

ID    n
--------
a     3
b     1
b2    1
c     2

下面的代码演示了如果在指定 'b' 之后开始,结果是预期的。

const Firestore = require('@google-cloud/firestore');
const db = new Firestore()

let query = db.collection('0');

db.collection('0').doc('b').get().then(b => {

    query.orderBy('n').startAfter(b).get().then(snapshot => {
        snapshot.forEach(document => {
            console.log(document.id + " " + document.get('n'));
        });
    });

});

这个returns

b2 1
c 2
a 3

如果你没有权限访问完整的对象,你可以在你的情况下除了喜欢之外还按id排序,然后在id和喜欢之后开始。

例如:

const lastRef = db.collection('0').doc('b');
const lastValue = "1"
query.orderBy('n').orderBy(Firestore.FieldPath.documentId()).startAfter(lastValue, lastRef).get().then(snapshot => {
    snapshot.forEach(document => {
        console.log(document.id + " " + document.get('n'));
    });
});

将最后一个文档从云功能发送到 Flutter 并返回,导致它在 startAt 查询中无法使用,原因不明。 (可能是因为它不能声明为 DocumentSnapshot)。

我所做的只是将最后一个文档 ID 发送到 Flutter,然后在加载更多文档时返回后端。加载额外数据时,我对该文档 ID 进行快照,startAt 该快照按预期工作。

这需要 1 个额外的文档快照,但在客户端和后端之间发送的数据较少,最重要的是它有效。

例如:

exports.loadTrendingPosts = functions.https.onCall(async(data, context) => {
   var startData = data.startAt; //either null(first load) or previous last documents ID
   var currentTime = admin.firestore.Timestamp.now();
   var lastDocument;
   var monthYearCompare = '202001';

   if(startData !== null && startData !== undefined){
    var lastDocSnap = await db.collection('posts').doc(startData).get(); //snapshot the last document from it's ID
    postSnap = await db.collection('posts')
        .where('nextTwelveMonths', 'array-contains', monthYearCompare)
        .orderBy('likes', 'desc')
        .startAfter(lastDocSnap)
        .limit(30)
        .get();
   }
   else{ //initial load
      postSnap = await db.collection('posts').where('nextTwelveMonths', 'array-contains', monthYearCompare).orderBy('likes', 'desc').limit(30).get();
      lastDocument = (postSnap.docs[postSnap.docs.length - 1]).id; //get the ID instead of the document

   }
   
  return [postsSnap, lastDocument];
});