Cloud Firestore รองรับการดำเนินการระดับอะตอมมิกสำหรับการอ่านและเขียนข้อมูล ในชุดของการดำเนินการแบบอะตอมมิก การดำเนินการทั้งหมดจะสำเร็จหรือไม่มีการใช้เลย การดำเนินการอะตอมมิกมีสองประเภทใน Cloud Firestore:
- ธุรกรรม : ธุรกรรม คือชุดของการดำเนินการอ่านและเขียนในเอกสารตั้งแต่หนึ่งฉบับขึ้นไป
- การเขียนแบบเป็นชุด : การเขียนแบบเป็นชุด คือชุดของการดำเนินการเขียนในเอกสารตั้งแต่หนึ่งฉบับขึ้นไป
การอัพเดตข้อมูลด้วยธุรกรรม
การใช้ไลบรารีไคลเอ็นต์ Cloud Firestore ทำให้คุณสามารถจัดกลุ่มการดำเนินการหลายอย่างให้เป็นธุรกรรมเดียวได้ ธุรกรรมมีประโยชน์เมื่อคุณต้องการอัปเดตค่าของฟิลด์ตามมูลค่าปัจจุบันหรือมูลค่าของฟิลด์อื่น
ธุรกรรมประกอบด้วยการดำเนินการ get()
จำนวนเท่าใดก็ได้ ตามด้วยการดำเนินการเขียนจำนวนเท่าใดก็ได้ เช่น set()
, update()
หรือ delete()
ในกรณีที่มีการแก้ไขพร้อมกัน Cloud Firestore จะดำเนินธุรกรรมทั้งหมดอีกครั้ง ตัวอย่างเช่น หากธุรกรรมอ่านเอกสารและไคลเอ็นต์อื่นแก้ไขเอกสารเหล่านั้น Cloud Firestore จะพยายามทำธุรกรรมอีกครั้ง คุณลักษณะนี้ช่วยให้มั่นใจได้ว่าธุรกรรมจะดำเนินการโดยใช้ข้อมูลที่ทันสมัยและสม่ำเสมอ
ธุรกรรมไม่เคยใช้การเขียนเพียงบางส่วน การเขียนทั้งหมดจะดำเนินการเมื่อสิ้นสุดการทำธุรกรรมที่ประสบความสำเร็จ
เมื่อใช้ธุรกรรม โปรดทราบว่า:
- การดำเนินการอ่านต้องมาก่อนการดำเนินการเขียน
- ฟังก์ชันที่เรียกใช้ธุรกรรม (ฟังก์ชันธุรกรรม) อาจทำงานมากกว่าหนึ่งครั้งหากการแก้ไขพร้อมกันส่งผลต่อเอกสารที่ธุรกรรมอ่าน
- ฟังก์ชันธุรกรรมไม่ควรแก้ไขสถานะแอปพลิเคชันโดยตรง
- ธุรกรรมจะล้มเหลวเมื่อไคลเอนต์ออฟไลน์
ตัวอย่างต่อไปนี้แสดงวิธีการสร้างและรันธุรกรรม:
Web modular API
import { runTransaction } from "firebase/firestore"; try { await runTransaction(db, async (transaction) => { const sfDoc = await transaction.get(sfDocRef); if (!sfDoc.exists()) { throw "Document does not exist!"; } const newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); console.log("Transaction successfully committed!"); } catch (e) { console.log("Transaction failed: ", e); }
Web namespaced API
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); // Uncomment to initialize the doc. // sfDocRef.set({ population: 0 }); return db.runTransaction((transaction) => { // This code may get re-run multiple times if there are conflicts. return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } // Add one person to the city population. // Note: this could be done without a transaction // by updating the population using FieldValue.increment() var newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); }).then(() => { console.log("Transaction successfully committed!"); }).catch((error) => { console.log("Transaction failed: ", error); });
สวิฟท์
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference) return nil }) { (object, error) in if let error = error { print("Transaction failed: \(error)") } else { print("Transaction successfully committed!") } }
วัตถุประสงค์-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue]; // Note: this could be done without a transaction // by updating the population using FieldValue.increment() [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Transaction successfully committed!"); } }];
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) // Note: this could be done without a transaction // by updating the population using FieldValue.increment() val newPopulation = snapshot.getDouble("population")!! + 1 transaction.update(sfDocRef, "population", newPopulation) // Success null }.addOnSuccessListener { Log.d(TAG, "Transaction success!") } .addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Void>() { @Override public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() double newPopulation = snapshot.getDouble("population") + 1; transaction.update(sfDocRef, "population", newPopulation); // Success return null; } }).addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "Transaction success!"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) async { final snapshot = await transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() final newPopulation = snapshot.get("population") + 1; transaction.update(sfDocRef, {"population": newPopulation}); }).then( (value) => print("DocumentSnapshot successfully updated!"), onError: (e) => print("Error updating document $e"), );
ชวา
หลาม
Python
ซี++
DocumentReference sf_doc_ref = db->Collection("cities").Document("SF"); db->RunTransaction([sf_doc_ref](Transaction& transaction, std::string& out_error_message) -> Error { Error error = Error::kErrorOk; DocumentSnapshot snapshot = transaction.Get(sf_doc_ref, &error, &out_error_message); // Note: this could be done without a transaction by updating the // population using FieldValue::Increment(). std::int64_t new_population = snapshot.Get("population").integer_value() + 1; transaction.Update( sf_doc_ref, {{"population", FieldValue::Integer(new_population)}}); return Error::kErrorOk; }).OnCompletion([](const Future<void>& future) { if (future.error() == Error::kErrorOk) { std::cout << "Transaction success!" << std::endl; } else { std::cout << "Transaction failure: " << future.error_message() << std::endl; } });
โหนด js
ไป
PHP
ความสามัคคี
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((snapshotTask) => { DocumentSnapshot snapshot = snapshotTask.Result; long newPopulation = snapshot.GetValue<long>("Population") + 1; Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); }); });
ค#
ทับทิม
การส่งข้อมูลออกจากธุรกรรม
อย่าแก้ไขสถานะแอปพลิเคชันภายในฟังก์ชันธุรกรรมของคุณ การทำเช่นนี้จะทำให้เกิดปัญหาการทำงานพร้อมกัน เนื่องจากฟังก์ชันธุรกรรมสามารถทำงานได้หลายครั้ง และไม่รับประกันว่าจะทำงานบนเธรด UI ให้ส่งข้อมูลที่คุณต้องการจากฟังก์ชันธุรกรรมของคุณแทน ตัวอย่างต่อไปนี้สร้างขึ้นจากตัวอย่างก่อนหน้านี้เพื่อแสดงวิธีการส่งข้อมูลออกจากธุรกรรม:
Web modular API
import { doc, runTransaction } from "firebase/firestore"; // Create a reference to the SF doc. const sfDocRef = doc(db, "cities", "SF"); try { const newPopulation = await runTransaction(db, async (transaction) => { const sfDoc = await transaction.get(sfDocRef); if (!sfDoc.exists()) { throw "Document does not exist!"; } const newPop = sfDoc.data().population + 1; if (newPop <= 1000000) { transaction.update(sfDocRef, { population: newPop }); return newPop; } else { return Promise.reject("Sorry! Population is too big"); } }); console.log("Population increased to ", newPopulation); } catch (e) { // This will be a "population is too big" error. console.error(e); }
Web namespaced API
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) => { return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } var newPopulation = sfDoc.data().population + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, { population: newPopulation }); return newPopulation; } else { return Promise.reject("Sorry! Population is too big."); } }); }).then((newPopulation) => { console.log("Population increased to ", newPopulation); }).catch((err) => { // This will be an "population is too big" error. console.error(err); });
สวิฟท์
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() let newPopulation = oldPopulation + 1 guard newPopulation <= 1000000 else { let error = NSError( domain: "AppErrorDomain", code: -2, userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"] ) errorPointer?.pointee = error return nil } transaction.updateData(["population": newPopulation], forDocument: sfReference) return newPopulation }) { (object, error) in if let error = error { print("Error updating population: \(error)") } else { print("Population increased to \(object!)") } }
วัตถุประสงค์-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger population = [sfDocument.data[@"population"] integerValue]; population++; if (population >= 1000000) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{ NSLocalizedDescriptionKey: @"Population too big" }]; return @(population); } [transaction updateData:@{ @"population": @(population) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Population increased to %@", result); } }];
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) val newPopulation = snapshot.getDouble("population")!! + 1 if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation) newPopulation } else { throw FirebaseFirestoreException( "Population too high", FirebaseFirestoreException.Code.ABORTED, ) } }.addOnSuccessListener { result -> Log.d(TAG, "Transaction success: $result") }.addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Double>() { @Override public Double apply(@NonNull Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); double newPopulation = snapshot.getDouble("population") + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation); return newPopulation; } else { throw new FirebaseFirestoreException("Population too high", FirebaseFirestoreException.Code.ABORTED); } } }).addOnSuccessListener(new OnSuccessListener<Double>() { @Override public void onSuccess(Double result) { Log.d(TAG, "Transaction success: " + result); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) { return transaction.get(sfDocRef).then((sfDoc) { final newPopulation = sfDoc.get("population") + 1; transaction.update(sfDocRef, {"population": newPopulation}); return newPopulation; }); }).then( (newPopulation) => print("Population increased to $newPopulation"), onError: (e) => print("Error updating document $e"), );
ชวา
หลาม
Python
ซี++
// This is not yet supported.
โหนด js
ไป
PHP
ความสามัคคี
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((task) => { long newPopulation = task.Result.GetValue<long>("Population") + 1; if (newPopulation <= 1000000) { Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); return true; } else { return false; } }); }).ContinueWith((transactionResultTask) => { if (transactionResultTask.Result) { Console.WriteLine("Population updated successfully."); } else { Console.WriteLine("Sorry! Population is too big."); } });
ค#
ทับทิม
ความล้มเหลวในการทำธุรกรรม
ธุรกรรมอาจล้มเหลวด้วยเหตุผลดังต่อไปนี้:
- ธุรกรรมประกอบด้วยการดำเนินการอ่านหลังจากการดำเนินการเขียน การดำเนินการอ่านจะต้องมาก่อนการดำเนินการเขียนใดๆ เสมอ
- ธุรกรรมอ่านเอกสารที่ได้รับการแก้ไขภายนอกธุรกรรม ในกรณีนี้ ธุรกรรมจะดำเนินการอีกครั้งโดยอัตโนมัติ ธุรกรรมถูกลองใหม่ตามจำนวนครั้งที่จำกัด
ธุรกรรมเกินขนาดคำขอสูงสุด 10 MiB
ขนาดธุรกรรมขึ้นอยู่กับขนาดของเอกสารและรายการดัชนีที่แก้ไขโดยธุรกรรม สำหรับการดำเนินการลบ รวมถึงขนาดของเอกสารเป้าหมายและขนาดของรายการดัชนีที่ถูกลบเพื่อตอบสนองต่อการดำเนินการ
ธุรกรรมที่ล้มเหลวส่งกลับข้อผิดพลาดและไม่ได้เขียนสิ่งใดลงในฐานข้อมูล คุณไม่จำเป็นต้องย้อนกลับธุรกรรม Cloud Firestore ดำเนินการนี้โดยอัตโนมัติ
เขียนเป็นชุด
หากคุณไม่ต้องการอ่านเอกสารใดๆ ในชุดการดำเนินการของคุณ คุณสามารถดำเนินการเขียนหลายรายการเป็นชุดเดียวที่ประกอบด้วยการดำเนินการ set()
, update()
หรือ delete()
ผสมกัน ชุดการเขียนจะเสร็จสิ้นแบบอะตอมมิกและสามารถเขียนลงในเอกสารหลายชุดได้ ตัวอย่างต่อไปนี้แสดงวิธีการสร้างและคอมมิตชุดการเขียน:
Web modular API
import { writeBatch, doc } from "firebase/firestore"; // Get a new write batch const batch = writeBatch(db); // Set the value of 'NYC' const nycRef = doc(db, "cities", "NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' const sfRef = doc(db, "cities", "SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' const laRef = doc(db, "cities", "LA"); batch.delete(laRef); // Commit the batch await batch.commit();
Web namespaced API
// Get a new write batch var batch = db.batch(); // Set the value of 'NYC' var nycRef = db.collection("cities").doc("NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' var sfRef = db.collection("cities").doc("SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' var laRef = db.collection("cities").doc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then(() => { // ... });
สวิฟท์
// Get new write batch let batch = db.batch() // Set the value of 'NYC' let nycRef = db.collection("cities").document("NYC") batch.setData([:], forDocument: nycRef) // Update the population of 'SF' let sfRef = db.collection("cities").document("SF") batch.updateData(["population": 1000000 ], forDocument: sfRef) // Delete the city 'LA' let laRef = db.collection("cities").document("LA") batch.deleteDocument(laRef) // Commit the batch batch.commit() { err in if let err = err { print("Error writing batch \(err)") } else { print("Batch write succeeded.") } }
วัตถุประสงค์-C
// Get new write batch FIRWriteBatch *batch = [self.db batch]; // Set the value of 'NYC' FIRDocumentReference *nycRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"]; [batch setData:@{} forDocument:nycRef]; // Update the population of 'SF' FIRDocumentReference *sfRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [batch updateData:@{ @"population": @1000000 } forDocument:sfRef]; // Delete the city 'LA' FIRDocumentReference *laRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"]; [batch deleteDocument:laRef]; // Commit the batch [batch commitWithCompletion:^(NSError * _Nullable error) { if (error != nil) { NSLog(@"Error writing batch %@", error); } else { NSLog(@"Batch write succeeded."); } }];
Kotlin+KTX
val nycRef = db.collection("cities").document("NYC") val sfRef = db.collection("cities").document("SF") val laRef = db.collection("cities").document("LA") // Get a new write batch and commit all write operations db.runBatch { batch -> // Set the value of 'NYC' batch.set(nycRef, City()) // Update the population of 'SF' batch.update(sfRef, "population", 1000000L) // Delete the city 'LA' batch.delete(laRef) }.addOnCompleteListener { // ... }
Java
// Get a new write batch WriteBatch batch = db.batch(); // Set the value of 'NYC' DocumentReference nycRef = db.collection("cities").document("NYC"); batch.set(nycRef, new City()); // Update the population of 'SF' DocumentReference sfRef = db.collection("cities").document("SF"); batch.update(sfRef, "population", 1000000L); // Delete the city 'LA' DocumentReference laRef = db.collection("cities").document("LA"); batch.delete(laRef); // Commit the batch batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // ... } });
Dart
// Get a new write batch final batch = db.batch(); // Set the value of 'NYC' var nycRef = db.collection("cities").doc("NYC"); batch.set(nycRef, {"name": "New York City"}); // Update the population of 'SF' var sfRef = db.collection("cities").doc("SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' var laRef = db.collection("cities").doc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then((_) { // ... });
ชวา
หลาม
Python
ซี++
// Get a new write batch WriteBatch batch = db->batch(); // Set the value of 'NYC' DocumentReference nyc_ref = db->Collection("cities").Document("NYC"); batch.Set(nyc_ref, {}); // Update the population of 'SF' DocumentReference sf_ref = db->Collection("cities").Document("SF"); batch.Update(sf_ref, {{"population", FieldValue::Integer(1000000)}}); // Delete the city 'LA' DocumentReference la_ref = db->Collection("cities").Document("LA"); batch.Delete(la_ref); // Commit the batch batch.Commit().OnCompletion([](const Future<void>& future) { if (future.error() == Error::kErrorOk) { std::cout << "Write batch success!" << std::endl; } else { std::cout << "Write batch failure: " << future.error_message() << std::endl; } });
โหนด js
ไป
PHP
ความสามัคคี
WriteBatch batch = db.StartBatch(); // Set the data for NYC DocumentReference nycRef = db.Collection("cities").Document("NYC"); Dictionary<string, object> nycData = new Dictionary<string, object> { { "name", "New York City" } }; batch.Set(nycRef, nycData); // Update the population for SF DocumentReference sfRef = db.Collection("cities").Document("SF"); Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", 1000000} }; batch.Update(sfRef, updates); // Delete LA DocumentReference laRef = db.Collection("cities").Document("LA"); batch.Delete(laRef); // Commit the batch batch.CommitAsync();
ค#
ทับทิม
การเขียนแบบแบตช์สามารถมีการดำเนินการได้สูงสุด 500 รายการ การดำเนินการแต่ละรายการในชุดจะนับแยกจากการใช้งาน Cloud Firestore ของคุณ
เช่นเดียวกับธุรกรรม การเขียนแบบแบตช์เป็นแบบอะตอมมิก การเขียนแบบเป็นชุดไม่จำเป็นต้องทำให้แน่ใจว่าเอกสารที่อ่านยังคงไม่มีการแก้ไข ซึ่งแตกต่างจากธุรกรรม ซึ่งนำไปสู่กรณีความล้มเหลวน้อยลง พวกเขาจะไม่ถูกลองใหม่หรือล้มเหลวจากการลองใหม่หลายครั้งเกินไป การเขียนแบบเป็นกลุ่มจะดำเนินการแม้ในขณะที่อุปกรณ์ของผู้ใช้ออฟไลน์
การตรวจสอบข้อมูลสำหรับการดำเนินงานของอะตอม
สำหรับไลบรารีไคลเอ็นต์อุปกรณ์เคลื่อนที่/เว็บ คุณสามารถตรวจสอบข้อมูลได้โดยใช้ กฎความปลอดภัยของ Cloud Firestore คุณสามารถมั่นใจได้ว่าเอกสารที่เกี่ยวข้องจะได้รับการอัปเดตแบบอะตอมมิกและเป็นส่วนหนึ่งของธุรกรรมหรือการเขียนเป็นชุดเสมอ ใช้ฟังก์ชันกฎความปลอดภัย getAfter()
เพื่อเข้าถึงและตรวจสอบสถานะของเอกสารหลังจากชุดการดำเนินการเสร็จสมบูรณ์ แต่ ก่อนที่ Cloud Firestore จะดำเนินการดำเนินการ
ตัวอย่างเช่น ลองจินตนาการว่าฐานข้อมูลสำหรับตัวอย่าง cities
มีคอลเล็กชัน countries
ด้วย เอกสารแต่ละ country
ใช้ช่อง last_updated
เพื่อติดตามเวลาล่าสุดที่เมืองที่เกี่ยวข้องกับประเทศนั้นได้รับการอัปเดต กฎความปลอดภัยต่อไปนี้กำหนดให้การอัปเดตเอกสาร city
ต้องอัปเดตฟิลด์ last_updated
ของประเทศที่เกี่ยวข้องแบบอะตอมมิกด้วย:
service cloud.firestore { match /databases/{database}/documents { // If you update a city doc, you must also // update the related country's last_updated field. match /cities/{city} { allow write: if request.auth != null && getAfter( /databases/$(database)/documents/countries/$(request.resource.data.country) ).data.last_updated == request.time; } match /countries/{country} { allow write: if request.auth != null; } } }
ข้อจำกัดของกฎความปลอดภัย
ในกฎความปลอดภัยสำหรับธุรกรรมหรือการเขียนเป็นชุด มี การจำกัด การเรียกเข้าถึงเอกสาร 20 ครั้งสำหรับการดำเนินการอะตอมมิกทั้งหมด นอกเหนือจากขีดจำกัดการเรียกปกติ 10 ครั้งสำหรับการดำเนินการเอกสารแต่ละรายการในชุดงาน
ตัวอย่างเช่น พิจารณากฎต่อไปนี้สำหรับแอปพลิเคชันแชท:
service cloud.firestore { match /databases/{db}/documents { function prefix() { return /databases/{db}/documents; } match /chatroom/{roomId} { allow read, write: if request.auth != null && roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats || exists(/$(prefix())/admins/$(request.auth.uid)); } match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId || exists(/$(prefix())/admins/$(request.auth.uid)); } match /admins/{userId} { allow read, write: if request.auth != null && exists(/$(prefix())/admins/$(request.auth.uid)); } } }
ตัวอย่างด้านล่างแสดงจำนวนการเรียกเข้าถึงเอกสารที่ใช้สำหรับรูปแบบการเข้าถึงข้อมูลบางรูปแบบ:
// 0 document access calls used, because the rules evaluation short-circuits // before the exists() call is invoked. db.collection('user').doc('myuid').get(...); // 1 document access call used. The maximum total allowed for this call // is 10, because it is a single document request. db.collection('chatroom').doc('mygroup').get(...); // Initializing a write batch... var batch = db.batch(); // 2 document access calls used, 10 allowed. var group1Ref = db.collection("chatroom").doc("group1"); batch.set(group1Ref, {msg: "Hello, from Admin!"}); // 1 document access call used, 10 allowed. var newUserRef = db.collection("users").doc("newuser"); batch.update(newUserRef, {"lastSignedIn": new Date()}); // 1 document access call used, 10 allowed. var removedAdminRef = db.collection("admin").doc("otheruser"); batch.delete(removedAdminRef); // The batch used a total of 2 + 1 + 1 = 4 document access calls, out of a total // 20 allowed. batch.commit();
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับวิธีแก้ไขปัญหาเวลาในการตอบสนองที่เกิดจากการเขียนจำนวนมากและการเขียนเป็นชุด ข้อผิดพลาดเนื่องจากการโต้แย้งจากธุรกรรมที่ทับซ้อนกัน และปัญหาอื่นๆ โปรดตรวจสอบ หน้าการแก้ปัญหา