【问题标题】:onUpdate and Transaction Document LockonUpdate 和事务文档锁
【发布时间】:2021-02-18 11:30:21
【问题描述】:

我有一个用户想要注册课程的案例。

如果课程已满,用户可以在等候名单上注册。如果此课程有空位,该用户将自动注册。为了实现等候名单自动化,我使用了带有.onUpdate 触发器的云函数 (CF),它可以工作。

CF 示例: 课程已满。参与者取消注册。 CF 触发并检查课程中是否有空闲位置以及等待列表中的用户是否尚未注册。如果是这样,那么 CF 会从课程的等待列表中注册用户。

同时,在 UI 中,当有空位可用时,我允许已在等候名单上的同一用户注册课程。通过注册课程,我将他从等候名单中删除。

对于这个实现,我使用了transaction,它仅在用户尚未在课程中注册时完成。

由于某种原因,有时用户会注册两次。

我的问题是:onUpdate 函数会锁定文档吗?我是否需要onUpdate 函数中的transaction 来监听同一文档的更改? => 对我来说这是没有意义的。

附:处理此错误的最简单方法是让用户先从等候名单中取消注册,然后再注册课程。不过,我想了解如何实现上述逻辑。

Flutter 中的事务:

@override
  Future<void> selfSetCourseParticipantWithStashedCreditPoint(
      String courseEventId, Participant participantToAdd) async {
    try {
      UsedCreditPointInfo participantsCreditPoint =
          participantToAdd.usedCreditPointInfo;
      await _firestore.runTransaction((transaction) async {
        // 1) Check if the customer does still have an available credit point left
        final userRef = _usersCollection.doc(participantToAdd.uid);
        var userDocumentSnapshot = await transaction.get(userRef);
        var stashedCreditPoints =
            userDocumentSnapshot.data()['${participantToAdd.creditPointPath}'];
        var creditPointExists = false;
        for (var usedCreditPointInfoMap in stashedCreditPoints) {
          var usedCreditPoint =
              UsedCreditPointInfo.fromMap(usedCreditPointInfoMap);
          if (usedCreditPoint == participantsCreditPoint) {
            creditPointExists = true;
            break;
          }
        }
        // if the credit point does not exist, then the transaction must fail
        if (!creditPointExists) return null;

        final courseRef = _courseEventsCollection.doc(courseEventId);
        final courseEventDocumentSnapshot = await transaction.get(courseRef);
        // 2) if the courseEvent document exists I have to check first if there are slots left
        var currentCourseEvent = CourseEvent.fromEntity(
            CourseEventEntity.fromSnapshot(courseEventDocumentSnapshot));
        //todo: add the remaining requirements, e.g., registration date limit
        if (currentCourseEvent.participants.length >=
            currentCourseEvent.maxParticipants) {
          return null;
        }

        if (currentCourseEvent.waitingList != null) {
          var waitingList = currentCourseEvent.waitingList;
          for (var i = 0; i < waitingList.length; i++) {
            final waitingListParticipant = waitingList[i];
            if (waitingListParticipant.uid == participantToAdd.uid) {
              waitingList.removeAt(i);
              break;
            }
          }
        }

        // add only if the participant is not already included to the list
        if (!currentCourseEvent.participants.contains(participantToAdd)) {
          currentCourseEvent = currentCourseEvent.copyWith(
            participants: List.from(currentCourseEvent.participants)
              ..add(participantToAdd),
          );

          // the user document exists at this point, thus update is sufficient
          transaction.update(
            userRef,
            {
              '${participantToAdd.creditPointPath}': FieldValue.arrayRemove(
                  [participantToAdd.usedCreditPointInfo?.toMap()])
            },
          );
          return transaction.set(
              courseRef, currentCourseEvent.toEntity().toMap());
        }
      });
    } catch (e, s) {
      return handleFirestoreError(e, s);
    }
  }

我的 CF 的 sn-p:

export const notifyOnCourseEventUpdateAndWaitingListRegistration = functions.firestore
  .document("CourseEvents/{courseEvent}")
  .onUpdate(async (change, context) => {
    return firestore
      .runTransaction(async (transaction) => {
        const courseEventRef = firestore
          .collection("CourseEvents")
          .doc(change.after.data().firebaseId);
        const courseEventSnapshot = await transaction.get(courseEventRef);
        const courseEvent = courseEventSnapshot.data() as CourseEvent;

        const currentCourseEvent =  change.after.data() as CourseEvent;
        const currentParticipants = currentCourseEvent.participants;
        const previousCourseEvent = change.before.data() as CourseEvent;
        const previousParticipants = previousCourseEvent.participants;
        const currentMaxParticipants = currentCourseEvent.maxParticipants;
        const currentWaitingList = currentCourseEvent.waitingList;
        ...
      })
    });

【问题讨论】:

    标签: firebase flutter google-cloud-firestore google-cloud-functions


    【解决方案1】:

    onUpdate 函数是否锁定文档?

    没有。

    我是否需要在 onUpdate 函数中进行事务来监听同一文档的更改?

    我不完全确定这个问题在问什么,但无论如何,如果您希望更新两个同时运行的代码以安全地更新同一个文档而不相互覆盖,那么您需要一个事务。

    事务不会“监听”文档中的更改。它们只是通过在检测到冲突时重试事务处理函数来确保两个客户端在对同一个文档进行事务处理时不会相互覆盖。

    【讨论】:

    • 啊,太好了。所以这意味着当.onUpdate() 触发时,文档仍然可以被覆盖,例如,从客户端。如果我在.onUpdate 中实现一个事务并将change.before.data()change.after.data() 放在事务中,无论事务重试的频率如何,这两个函数检索到的数据都会是初始数据吗?
    • 是的,这是真的。触发器在文档更改后运行一段时间,并且不按任何给定顺序执行。如果您认为触发器执行时文档可能再次更改,则应再次读取文档并进行处理。
    猜你喜欢
    • 1970-01-01
    • 2014-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 2020-10-29
    相关资源
    最近更新 更多