flutter firestore 事务在写入之前读取错误
flutter firestore transaction reads before writes give error
这是我正在尝试的代码 运行:
class TransactionEntity {
final Entity entity;
final DocumentReference<Map<String, dynamic>> document;
DocumentSnapshot? snapshot;
final String writeOperation;
TransactionEntity(
{required this.entity,
required this.document,
required this.writeOperation,
this.snapshot});
void set(DocumentSnapshot documentSnapshot) {
snapshot = documentSnapshot;
}
DocumentSnapshot? get documentSnapshot {
return snapshot;
}
Transaction? executeWriteOperation(Transaction transaction) {
if (writeOperation == "update") {
Map<String, dynamic> attributesToUpdate = {};
var entityData = entity.toJson();
if (snapshot != null) {
var data = snapshot!.data() as Map<String, dynamic>;
entityData.forEach((attributeName, attributeValue) {
if (attributeValue is List) {
if (!listEquals(attributeValue, data[attributeName])) {
attributesToUpdate[attributeName] = data[attributeName];
}
} else if (attributeValue is Entity || attributeValue is Map) {
var nestedMap = {};
var nestedObject = attributeValue.toJson();
nestedObject.forEach((key, value) {
if (value != data[attributeName][key]) {
String newKey = "$attributeName.$key";
nestedMap[newKey] = data[attributeName][key];
}
});
if (nestedMap.isNotEmpty) {
attributesToUpdate[attributeName] = nestedMap;
}
} else if (data[attributeName] != attributeValue) {
attributesToUpdate[attributeName] = attributeValue;
}
});
}
return transaction.update(document, attributesToUpdate);
} else if (writeOperation == "set") {
return transaction.set(document, entity.toJson());
} else if (writeOperation == "delete" && snapshot!.exists) {
return transaction.delete(document);
}
return null;
}
}
Future<void> updateProductTransaction(
Product product,
int nLocations,
) async {
try {
List<ProductVariant> productVariants =
product.variants == null || product.variants!.isEmpty
? [product.defaultVariant!]
: product.variants!;
List<ProductOption> productOptions = product.options ?? [];
DocumentReference<Map<String, dynamic>> productDocument =
FirebaseFirestore.instance.collection("product").doc(product.id);
List<DocumentReference<Map<String, dynamic>>> productVariantDocuments =
await FirebaseFirestore.instance
.collectionGroup("productVariant")
.where("parentId", isEqualTo: product.id)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
Map<String, List<DocumentReference<Map<String, dynamic>>?>>
inventoryItemDocuments = {};
for (var variant in productVariants) {
await FirebaseFirestore.instance
.collectionGroup("inventoryItem")
.where("parentId", isEqualTo: variant.id)
.get()
.then((value) {
inventoryItemDocuments[variant.id!] =
value.docs.map((e) => e.reference).toList();
});
if (inventoryItemDocuments.isEmpty) {
inventoryItemDocuments[variant.id!] =
List.generate(nLocations, (index) => null);
}
}
List<DocumentReference<Map<String, dynamic>>> productOptionDocuments = [];
if (productOptions.isNotEmpty) {
productOptionDocuments = await FirebaseFirestore.instance
.collectionGroup("productOption")
.where("parentId", isEqualTo: product.parentId)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
}
return await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentSnapshot productSnapshot =
await transaction.get(productDocument);
if (!productSnapshot.exists) {
throw EntityException("Prodotto non esistente!");
}
List<TransactionEntity> transactions = [];
transactions.add(TransactionEntity(
entity: product,
document: productDocument,
writeOperation: "update",
snapshot: productSnapshot,
));
for (var productVariant in productVariants) {
DocumentSnapshot? productVariantSnapshot = await transaction.get(
productVariantDocuments.firstWhere(
(variant) => variant.id == productVariant.id,
orElse: null));
var productVariantDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}}");
if (productVariantSnapshot.exists) {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "update",
snapshot: productVariantSnapshot,
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
var doc = inventoryItemDocuments[productVariant.id]!.firstWhere(
(item) => item?.id == inventoryItem.id,
orElse: null);
DocumentSnapshot? inventoryItemSnapshot;
if (doc != null) await transaction.get(doc);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "update",
snapshot: inventoryItemSnapshot,
));
}
} else {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "set",
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "set",
));
}
}
}
for (var optionDocument in productOptionDocuments) {
DocumentSnapshot? productOptionSnapshot =
await transaction.get(optionDocument);
var option = productOptions
.firstWhere((option) => option.id == optionDocument.id);
var productOptionDocument = FirebaseFirestore.instance
.doc("{product/${product.id}/productOption/${option.id}}");
if (productOptionSnapshot.exists) {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "update",
snapshot: productOptionSnapshot,
));
} else {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "set"));
}
}
} catch (e, stacktrace) {
print(stacktrace);
throw e;
}
}
当它 运行 我得到一个错误:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
packages/firebase_core/src/internals.dart 91:18 <fn>
dart-sdk/lib/async/zone.dart 1687:54 runUnary
dart-sdk/lib/async/future_impl.dart 178:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
dart-sdk/lib/async/future_impl.dart 800:13 _propagateToListeners
dart-sdk/lib/async/future_impl.dart 610:5 [_completeError]
dart-sdk/lib/async/future_impl.dart 879:16 <fn>
dart-sdk/lib/async/zone.dart 1692:54 runBinary
dart-sdk/lib/async/future_impl.dart 175:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
正如您在代码中看到的,所有读取操作都在写入操作之前执行。
我在 updateProductTransaction
方法中使用了一个 catch,但为什么没有捕获到那个异常?
TransactionEntity 收集文档和操作以在读取后执行。
ProductVariant、ProductOptions 是 Product 集合的子集合。
InventoryItem 是 ProductVariant 子集合的子集合。
由于路径错误,找不到文档,例如:
"{product/${product.id}/productVariant/${productVariant.id}}");
我不得不删除“{”、“}”。
这是我正在尝试的代码 运行:
class TransactionEntity {
final Entity entity;
final DocumentReference<Map<String, dynamic>> document;
DocumentSnapshot? snapshot;
final String writeOperation;
TransactionEntity(
{required this.entity,
required this.document,
required this.writeOperation,
this.snapshot});
void set(DocumentSnapshot documentSnapshot) {
snapshot = documentSnapshot;
}
DocumentSnapshot? get documentSnapshot {
return snapshot;
}
Transaction? executeWriteOperation(Transaction transaction) {
if (writeOperation == "update") {
Map<String, dynamic> attributesToUpdate = {};
var entityData = entity.toJson();
if (snapshot != null) {
var data = snapshot!.data() as Map<String, dynamic>;
entityData.forEach((attributeName, attributeValue) {
if (attributeValue is List) {
if (!listEquals(attributeValue, data[attributeName])) {
attributesToUpdate[attributeName] = data[attributeName];
}
} else if (attributeValue is Entity || attributeValue is Map) {
var nestedMap = {};
var nestedObject = attributeValue.toJson();
nestedObject.forEach((key, value) {
if (value != data[attributeName][key]) {
String newKey = "$attributeName.$key";
nestedMap[newKey] = data[attributeName][key];
}
});
if (nestedMap.isNotEmpty) {
attributesToUpdate[attributeName] = nestedMap;
}
} else if (data[attributeName] != attributeValue) {
attributesToUpdate[attributeName] = attributeValue;
}
});
}
return transaction.update(document, attributesToUpdate);
} else if (writeOperation == "set") {
return transaction.set(document, entity.toJson());
} else if (writeOperation == "delete" && snapshot!.exists) {
return transaction.delete(document);
}
return null;
}
}
Future<void> updateProductTransaction(
Product product,
int nLocations,
) async {
try {
List<ProductVariant> productVariants =
product.variants == null || product.variants!.isEmpty
? [product.defaultVariant!]
: product.variants!;
List<ProductOption> productOptions = product.options ?? [];
DocumentReference<Map<String, dynamic>> productDocument =
FirebaseFirestore.instance.collection("product").doc(product.id);
List<DocumentReference<Map<String, dynamic>>> productVariantDocuments =
await FirebaseFirestore.instance
.collectionGroup("productVariant")
.where("parentId", isEqualTo: product.id)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
Map<String, List<DocumentReference<Map<String, dynamic>>?>>
inventoryItemDocuments = {};
for (var variant in productVariants) {
await FirebaseFirestore.instance
.collectionGroup("inventoryItem")
.where("parentId", isEqualTo: variant.id)
.get()
.then((value) {
inventoryItemDocuments[variant.id!] =
value.docs.map((e) => e.reference).toList();
});
if (inventoryItemDocuments.isEmpty) {
inventoryItemDocuments[variant.id!] =
List.generate(nLocations, (index) => null);
}
}
List<DocumentReference<Map<String, dynamic>>> productOptionDocuments = [];
if (productOptions.isNotEmpty) {
productOptionDocuments = await FirebaseFirestore.instance
.collectionGroup("productOption")
.where("parentId", isEqualTo: product.parentId)
.get()
.then((value) => value.docs.map((e) => e.reference).toList());
}
return await FirebaseFirestore.instance
.runTransaction((transaction) async {
DocumentSnapshot productSnapshot =
await transaction.get(productDocument);
if (!productSnapshot.exists) {
throw EntityException("Prodotto non esistente!");
}
List<TransactionEntity> transactions = [];
transactions.add(TransactionEntity(
entity: product,
document: productDocument,
writeOperation: "update",
snapshot: productSnapshot,
));
for (var productVariant in productVariants) {
DocumentSnapshot? productVariantSnapshot = await transaction.get(
productVariantDocuments.firstWhere(
(variant) => variant.id == productVariant.id,
orElse: null));
var productVariantDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}}");
if (productVariantSnapshot.exists) {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "update",
snapshot: productVariantSnapshot,
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
var doc = inventoryItemDocuments[productVariant.id]!.firstWhere(
(item) => item?.id == inventoryItem.id,
orElse: null);
DocumentSnapshot? inventoryItemSnapshot;
if (doc != null) await transaction.get(doc);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "update",
snapshot: inventoryItemSnapshot,
));
}
} else {
transactions.add(TransactionEntity(
entity: productVariant,
document: productVariantDocument,
writeOperation: "set",
));
for (var inventoryItem in productVariant.inventoryItems!) {
var inventoryItemDocument = FirebaseFirestore.instance.doc(
"{product/${product.id}/productVariant/${productVariant.id}/inventoryItem/${inventoryItem.id}",
);
transactions.add(TransactionEntity(
entity: inventoryItem,
document: inventoryItemDocument,
writeOperation: "set",
));
}
}
}
for (var optionDocument in productOptionDocuments) {
DocumentSnapshot? productOptionSnapshot =
await transaction.get(optionDocument);
var option = productOptions
.firstWhere((option) => option.id == optionDocument.id);
var productOptionDocument = FirebaseFirestore.instance
.doc("{product/${product.id}/productOption/${option.id}}");
if (productOptionSnapshot.exists) {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "update",
snapshot: productOptionSnapshot,
));
} else {
transactions.add(TransactionEntity(
entity: option,
document: productOptionDocument,
writeOperation: "set"));
}
}
} catch (e, stacktrace) {
print(stacktrace);
throw e;
}
}
当它 运行 我得到一个错误:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49 throw_
packages/firebase_core/src/internals.dart 91:18 <fn>
dart-sdk/lib/async/zone.dart 1687:54 runUnary
dart-sdk/lib/async/future_impl.dart 178:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
dart-sdk/lib/async/future_impl.dart 800:13 _propagateToListeners
dart-sdk/lib/async/future_impl.dart 610:5 [_completeError]
dart-sdk/lib/async/future_impl.dart 879:16 <fn>
dart-sdk/lib/async/zone.dart 1692:54 runBinary
dart-sdk/lib/async/future_impl.dart 175:22 handleError
dart-sdk/lib/async/future_impl.dart 779:46 handleError
正如您在代码中看到的,所有读取操作都在写入操作之前执行。
我在 updateProductTransaction
方法中使用了一个 catch,但为什么没有捕获到那个异常?
TransactionEntity 收集文档和操作以在读取后执行。
ProductVariant、ProductOptions 是 Product 集合的子集合。
InventoryItem 是 ProductVariant 子集合的子集合。
由于路径错误,找不到文档,例如:
"{product/${product.id}/productVariant/${productVariant.id}}");
我不得不删除“{”、“}”。