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;
...
})
});