Cloud Firestore รองรับการดำเนินการระดับอะตอม สำหรับการอ่านและการเขียนข้อมูล ในชุดการดำเนินการแบบอะตอม การดำเนินการทั้งหมดจะสำเร็จหรือจะใช้งานการดำเนินการใดเลยไม่ได้ การดำเนินการแบบอะตอมใน Cloud Firestore มี 2 ประเภท ได้แก่
- ธุรกรรม: ธุรกรรมคือชุดการดำเนินการอ่านและเขียนในเอกสารอย่างน้อย 1 รายการ
- การเขียนแบบเป็นกลุ่ม: การเขียนแบบเป็นกลุ่มคือชุดการดำเนินการเขียนในเอกสารตั้งแต่ 1 รายการขึ้นไป
การอัปเดตข้อมูลด้วยธุรกรรม
คุณจัดกลุ่มการดำเนินการหลายรายการไว้ในธุรกรรมเดียวได้โดยใช้ไลบรารีของไคลเอ็นต์ Cloud Firestore ธุรกรรมจะมีประโยชน์เมื่อคุณต้องการอัปเดตค่าของฟิลด์ตามค่าปัจจุบันของฟิลด์นั้นหรือค่าของฟิลด์อื่น
ธุรกรรมประกอบด้วยการดำเนินการ get()
จำนวนเท่าใดก็ได้ตามด้วยการดำเนินการเขียนจำนวนเท่าใดก็ได้ เช่น set()
, update()
หรือ delete()
ในกรณีที่มีการแก้ไขพร้อมกัน Cloud Firestore จะเรียกใช้ทั้งธุรกรรมอีกครั้ง ตัวอย่างเช่น หากธุรกรรมอ่านเอกสารและลูกค้ารายอื่นทำการแก้ไขเอกสารเหล่านั้น Cloud Firestore จะพยายามทำธุรกรรมนั้นอีกครั้ง ฟีเจอร์นี้ช่วยให้มั่นใจว่าธุรกรรมจะดำเนินการโดยใช้ข้อมูลล่าสุดและสอดคล้องกัน
การเขียนธุรกรรม จะไม่นำไปใช้บางส่วน การเขียนทั้งหมดจะดำเนินการเมื่อธุรกรรมเสร็จสมบูรณ์
เมื่อใช้ธุรกรรม โปรดทราบว่า
- การดำเนินการอ่านต้องอยู่ก่อนการดำเนินการเขียน
- ฟังก์ชันที่เรียกธุรกรรม (ฟังก์ชันธุรกรรม) อาจทำงานมากกว่า 1 ครั้งหากการแก้ไขพร้อมกันส่งผลต่อเอกสารที่ธุรกรรมอ่าน
- ฟังก์ชันธุรกรรมไม่ควรแก้ไขสถานะแอปพลิเคชันโดยตรง
- ธุรกรรมจะล้มเหลวเมื่อลูกค้าออฟไลน์
ตัวอย่างต่อไปนี้แสดงวิธีสร้างและเรียกใช้ธุรกรรม
Web
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
// 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); });
Swift
let sfReference = db.collection("cities").document("SF") do { let _ = try await 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 }) print("Transaction successfully committed!") } catch { print("Transaction failed: \(error)") }
Objective-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"), );
Java
Python
Python
C++
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; } });
Node.js
Go
PHP
Unity
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); }); });
C#
Ruby
การส่งข้อมูลออกจากธุรกรรม
อย่าแก้ไขสถานะแอปพลิเคชันภายในฟังก์ชันธุรกรรม การทำเช่นนี้จะทำให้เกิดปัญหาการเกิดขึ้นพร้อมกัน เนื่องจากฟังก์ชันธุรกรรมทำงานได้หลายครั้งและไม่รับประกันว่าจะทำงานในเธรด UI ได้ แต่ให้ส่งข้อมูลที่จำเป็น ออกจากฟังก์ชันธุรกรรม ตัวอย่างต่อไปนี้จะต่อยอดจากตัวอย่างก่อนหน้าเพื่อแสดงวิธีส่งข้อมูลจากธุรกรรม
Web
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
// 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); });
Swift
let sfReference = db.collection("cities").document("SF") do { let object = try await 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 }) print("Population increased to \(object!)") } catch { print("Error updating population: \(error)") }
Objective-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"), );
Java
Python
Python
C++
// This is not yet supported.
Node.js
Go
PHP
Unity
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."); } });
C#
Ruby
ทำธุรกรรมไม่สำเร็จ
ธุรกรรมอาจดำเนินการไม่สำเร็จเนื่องจากสาเหตุต่อไปนี้
- ธุรกรรมมีการดำเนินการอ่านหลังจากการดำเนินการเขียน การดำเนินการอ่านต้องมาก่อนการดำเนินการเขียนเสมอ
- ธุรกรรมอ่านเอกสารที่มีการแก้ไขนอกธุรกรรม ในกรณีนี้ ธุรกรรมจะทำงานอีกครั้งโดยอัตโนมัติ ระบบจะลองทำธุรกรรมใหม่ตามจำนวนครั้งที่กำหนด
ธุรกรรมมีขนาดเกินขนาดคำขอสูงสุด 10 MiB
ขนาดธุรกรรมขึ้นอยู่กับขนาดของเอกสารและรายการดัชนีที่ธุรกรรมแก้ไข สำหรับการดำเนินการลบ ข้อมูลนี้รวมถึงขนาดของเอกสารเป้าหมายและขนาดของรายการดัชนีที่ลบตามการดำเนินการ
ธุรกรรมที่ไม่สําเร็จจะแสดงข้อผิดพลาดและไม่เขียนข้อมูลลงในฐานข้อมูล คุณไม่จำเป็นต้องย้อนกลับธุรกรรม เนื่องจาก Cloud Firestore ดำเนินการนี้โดยอัตโนมัติ
การเขียนเป็นกลุ่ม
หากไม่จำเป็นต้องอ่านเอกสารในชุดการดำเนินการ คุณใช้การดำเนินการเขียนหลายรายการเป็นกลุ่มเดียวที่มีชุดค่าผสมของการดำเนินการ set()
, update()
หรือ delete()
ได้
การดำเนินการแต่ละรายการในกลุ่มจะนับแยกกันสำหรับการใช้งาน Cloud Firestore การเขียนหลายรายการจะเสร็จสมบูรณ์พร้อมกันและสามารถเขียนลงในเอกสารหลายรายการได้ ตัวอย่างต่อไปนี้แสดงวิธีสร้างและมอบหมายกลุ่มการเขียน
Web
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
// 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(() => { // ... });
Swift
// 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 do { try await batch.commit() print("Batch write succeeded.") } catch { print("Error writing batch: \(error)") }
Objective-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((_) { // ... });
Java
Python
Python
C++
// 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; } });
Node.js
Go
PHP
Unity
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();
C#
Ruby
การเขียนแบบกลุ่มนั้นเหมือนกับธุรกรรม การเขียนแบบกลุ่มเป็นอะตอม ซึ่งแตกต่างจากธุรกรรมตรงที่การเขียนแบบเป็นกลุ่มไม่จําเป็นต้องตรวจสอบว่าเอกสารที่อ่านจะไม่มีการแก้ไข ซึ่งทําให้กรณีการดําเนินการที่ไม่สําเร็จมีน้อยลง ระบบจึงไม่ต้องลองใหม่หรือ ล้มเหลวจากการลองผิดลองถูกหลายครั้งเกินไป การเขียนแบบกลุ่มจะดำเนินการได้แม้อุปกรณ์ของผู้ใช้จะออฟไลน์อยู่
การเขียนแบบเป็นกลุ่มซึ่งมีเอกสารหลายร้อยรายการอาจต้องมีการอัปเดตดัชนีหลายรายการ และอาจเกินขีดจำกัดขนาดธุรกรรม ในกรณีนี้ ให้ลดจำนวนเอกสารต่อกลุ่ม หากต้องการเขียนเอกสารจํานวนมาก ให้พิจารณาใช้โปรแกรมเขียนไฟล์จํานวนมากหรือการเขียนแต่ละรายการแบบขนานแทน
การตรวจสอบข้อมูลสำหรับการดำเนินการระดับอะตอม
สำหรับไลบรารีของไคลเอ็นต์บนอุปกรณ์เคลื่อนที่/เว็บ คุณตรวจสอบข้อมูลได้โดยใช้ Cloud Firestore Security Rules คุณสามารถมั่นใจได้ว่าเอกสารที่เกี่ยวข้องจะได้รับการอัปเดตแบบเป็นกลุ่มเสมอและเป็นส่วนหนึ่งของธุรกรรมหรือการเขียนแบบเป็นกลุ่มเสมอ
ใช้ฟังก์ชันกฎความปลอดภัย 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)); } } }
ตัวอย่างด้านล่างแสดงจํานวนการเรียกใช้การเข้าถึงเอกสารที่ใช้กับรูปแบบการเข้าถึงข้อมูล 2-3 รูปแบบ
// 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();
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีแก้ไขปัญหาเวลาในการตอบสนองที่เกิดจากการเขียนขนาดใหญ่และการเขียนเป็นกลุ่ม ข้อผิดพลาดเนื่องจากการช่วงชิงธุรกรรมทับซ้อน และปัญหาอื่นๆ โปรดดูหน้าการแก้ปัญหา