将数据写入同步 Realm — Flutter SDK
使用Flexible Sync将数据写入同步 Realm 时,您可以使用与写入本地 Realm 相同的 API。 但是,在开发应用程序时,需要记住一些行为差异。 要了解有关在域中读取和写入数据的更多信息,请参阅读取和写入数据。
当您写入同步 Realm 时,写入操作必须匹配以下两项:
- 同步订阅查询。
如果写入操作与订阅中的查询不匹配,则写入将恢复并显示非致命补偿写入错误 (ErrorCompensatingWrite)。
要学习;了解有关补偿写入错误以及如何避免这些错误的更多信息,请参阅补偿写入部分。
- App Services App 中的权限。
如果尝试写入与权限表达式不匹配的数据,写入操作将恢复为非致命权限被拒绝错误。 在客户端中,这显示为错误 (ErrorCompensatingWrite)。 在服务器上,您可以查看有关角色中的写入筛选器如何拒绝写入的更多详细信息。
要了解有关为应用程序配置权限的更多信息,请参阅 Device SyncAtlas App Services文档中 的基于角色的权限 和 权限指南 。
要了解有关权限被拒绝错误、补偿写入错误和其他Device Sync 错误类型的更多信息,请参阅 Atlas App Services文档中的 同步错误 。
确定同步哪些数据
本页上的示例使用具有以下 Device Sync 配置的 Atlas App Services App 以及具有以下 Realm 软件开发工具包(Realm SDK) Realm 数据模型和订阅的客户端应用程序。
App Services 配置
Device Sync 配置有以下可查询字段:
_id
(始终包含在内)miles
ownerId
App Services App 的权限配置为仅允许用户读取和写入自己的数据:
{ "name": "owner-read-write", "apply_when": {}, "document_filters": { "read": { "ownerId": "%%user.id" }, "write": { "ownerId": "%%user.id" } }, "read": true, "write": true }
客户端Realm 数据模型和配置
本页中的示例使用以下模式:
()class _Car { "_id") ( () late ObjectId id; // This is the queryable field late String ownerId; late String make; late String? model; late int? miles; }
使用该模式,示例将同步域配置为同步与此订阅查询匹配的对象:
final app = App(AppConfiguration(APP_ID)); final user = await app.logIn(Credentials.anonymous()); final config = Configuration.flexibleSync(user, [Car.schema]); final realm = Realm(config); // Add subscriptions realm.subscriptions.update((mutableSubscriptions) { // Get Cars from Atlas that match the Realm Query Language query. // Uses the queryable field `miles`. // Query matches cars with less than 100 miles or `null` miles. final newCarQuery = realm.query<Car>("miles < 100 OR miles == \$0", [null]); mutableSubscriptions.add(newCarQuery, name: "new-car-subscription"); }); await realm.subscriptions.waitForSynchronization();
写入同步 Realm
对 Flexible Sync Realm 的写入大致可分为以下两类之一:
成功写入:写入的对象同时与查询订阅和用户权限匹配。 该对象成功写入到域,并成功同步到 App Services 后端和其他设备。
补偿写入:当写入的对象与订阅查询不匹配,或者用户没有足够的权限来执行写入时,Realm 将恢复非法写入。
成功写入
当写入操作同时与Atlas App Services权限和客户端中的Flexible Sync订阅查询匹配时, Realm Flutter SDK可以成功将该对象写入同步 Realm。 当设备具有网络连接时,此对象会与Atlas App Services后端同步。
// Per the Device Sync permissions, users can only read and write data // where the `Car.ownerId` property matches their own user ID. final userId = user.id; realm.write(() { // WRITE SUCCEEDS // `newCar` is successfully written to the realm and synced to Atlas // because it's data matches the subscription query (miles < 100) // and it's `ownerId` field matches the user ID. final newCar = Car(ObjectId(), userId, 'Toyota', miles: 2); realm.add(newCar); });
补偿写入
在某些情况下,最初看似成功的写入实际上是非法写入。 在这些情况下,对象会写入数据库,但当数据库同步到后端时,Realm 会在称为补偿写入的非致命错误操作中恢复写入。 在以下情况下可能会进行补偿写入:
写入与查询订阅不匹配:写入的对象与用户的权限匹配,但与查询订阅不匹配。
写入与权限不匹配:写入的对象与查询订阅匹配,但与用户的权限不匹配。
更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:
由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。
同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。
服务器向客户端发回恢复操作(称为“补偿写入”)。
客户端 Realm 恢复非法写入操作。
在对给定对象的非法写入和相应的补偿写入之间,客户端对该对象的任何写入都将丢失。
实际上,这可能看起来像一个对象被写入域,然后在服务器将补偿写操作发送回客户端后消失。
要了解有关权限被拒绝错误、补偿写入错误和其他Device Sync 错误类型的更多信息,请参阅 Atlas App Services文档中的 同步错误 。
Atlas App Services日志包含有关出现补偿写入错误的原因的更多信息。
与查询订阅不匹配的写入
只有与订阅查询匹配的对象,才能将其写入 Flexible Sync 域。如果您执行的写入与订阅查询不匹配,Realm 首先会写入对象,然后执行补偿写入。 这是一个非致命操作,可恢复与订阅查询不匹配的非法写入。
如果要写入与查询订阅不匹配的对象,请打开该对象与查询订阅匹配的另一个域。或者,您可以将对象写入不强制执行权限或订阅查询的本地 域 。
代码示例
根据上述同步域的配置,尝试写入此对象与查询订阅不匹配:
final carId = ObjectId(); final ownerId = app.currentUser!.id; realm.write(() { // WRITE REVERTED BY QUERY SUBSCRIPTION COMPENSATING WRITE // `oldCar` is initially written to the realm, then later removed // in a compensating write when the server processes the write. // This is because the `miles` property of `oldCar` doesn't match // the subscription query, which is only for cars with less than 100 miles. final oldCar = Car(carId, ownerId, 'Honda', miles: 90000); realm.add(oldCar); }); // Let changes sync to and from server await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
客户端错误
在这种情况下,客户端日志中的错误消息为:
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 21 in server version 2877: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
App Services 错误
在这种情况下,App Services 日志中的错误消息为:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Car": { "6424433fd4d9f52ee93ad590": "write to \"6424433fd4d9f52ee93ad590\" in table \"Car\" not allowed; object is outside of the current query view" } }
与权限不匹配的写入
当对象与用户的服务器端写入权限不匹配时,尝试写入客户端也可能会trigger补偿写入错误。
在客户端上,此类写入的行为与不匹配查询订阅的写入行为相同。 实际上,这可能看起来像写入成功,但当 Realm 与 App Services 后端同步并执行补偿写入时,该对象“消失”。
代码示例
考虑到上述 Device Sync 配置中的权限,尝试写入ownerId
属性与登录用户的user.id
不匹配的对象不是合法写入:
final carId = 'someOtherId'; realm.write(() { // WRITE REVERTED BY PERMISSION ERROR // `otherUsersCar` is initially written to the realm, then removed upon synchronization // because it's `ownerId` property doesn't match the user ID of the user // making the request. final otherUsersCar = Car(ObjectId(), carId, 'Ford'); realm.add(otherUsersCar); }); // sync changes await realm.syncSession.waitForUpload(); await realm.syncSession.waitForDownload(); final noCar = realm.find<Car>(carId); // The Car is no longer in the realm because of // the compensating write from the server. expect(noCar, isNull);
客户端错误
这种情况下的客户端错误与您尝试写入 App Services 权限之外的对象时出现的客户端错误相同:
[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" (error_code=231, try_again=true, error_action=Warning) [INFO] Realm: Connection[1]: Session[1]: Reporting compensating write for client version 25 in server version 2879: Client attempted a write that is outside of permissions or query filters; it has been reverted [ERROR] Realm: SyncSessionError message: Client attempted a write that is outside of permissions or query filters; it has been reverted Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false
App Services 错误
App Services 日志中的错误消息提供了一些附加信息,可帮助你确定这是权限问题,而不是查询订阅问题。 在此示例中,错误消息显示对象与用户角色不匹配:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Car": { "6424433fd4d9f52ee93ad591": "write to \"6424433fd4d9f52ee93ad591\" in table \"Car\" was denied by write filter in role \"owner-read-write\"" } }