Firebase onUpdate和事务文档锁

Firebase onUpdate和事务文档锁,firebase,flutter,google-cloud-firestore,google-cloud-functions,Firebase,Flutter,Google Cloud Firestore,Google Cloud Functions,我有一个案例,用户想要注册一门课程 如果课程已满,用户可以在等待名单上注册。如果为本课程打开一个免费插槽,该用户将自动注册。为了实现等待列表自动化,我使用了一个带有.onUpdate触发器的云函数(CF),它可以工作 CF示例:课程已满。参与者取消注册。CF启动并检查课程中是否有空闲时间,以及等待名单上的用户是否尚未注册。如果是,则CF从课程的等待列表中注册用户 同时,在UI中,当一个插槽可用时,我允许已经在等待名单上的同一用户注册课程。通过注册这门课程,我把他从候补名单上除名了 对于这个实现,

我有一个案例,用户想要注册一门课程

如果课程已满,用户可以在等待名单上注册。如果为本课程打开一个免费插槽,该用户将自动注册。为了实现等待列表自动化,我使用了一个带有
.onUpdate
触发器的云函数(CF),它可以工作

CF示例:课程已满。参与者取消注册。CF启动并检查课程中是否有空闲时间,以及等待名单上的用户是否尚未注册。如果是,则CF从课程的等待列表中注册用户

同时,在UI中,当一个插槽可用时,我允许已经在等待名单上的同一用户注册课程。通过注册这门课程,我把他从候补名单上除名了

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

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

我的问题是:更新
函数是否锁定文档?我是否需要在
onUpdate
函数中使用
事务
,以侦听同一文档的更改?=>对我来说,这毫无意义

另外,处理这个错误最简单的方法是让用户先从等待名单中注销,然后注册课程。不过,我想了解如何实现上述逻辑

在颤振事务中:

@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);
    }
  }
onUpdate函数是否锁定文档

没有

我是否需要onUpdate函数中的事务来侦听同一文档的更改

我不完全确定这个问题是什么,但在任何情况下,如果您希望更新两位并发运行的代码,以安全地更新同一文档而不相互覆盖,那么您需要一个事务


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

。因此,这意味着当触发
.onUpdate()
时,文档仍然可以被覆盖,例如,从客户端。如果我在
.onUpdate
中实现一个事务,并将
change.before.data()
change.after.data()
放在事务中,那么无论事务重试的频率有多高,这两个函数检索到的数据都将是初始数据吗?是的。触发器在文档更改后运行一段时间,不会按任何给定顺序执行。如果您认为在触发器执行时文档可能再次更改,则应该再次读取文档并对其进行处理。
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;
        ...
      })
    });