Docs 菜单
Docs 主页
/ /
Atlas Device SDKs
/ /

将数据写入同步 Realm — Flutter SDK

在此页面上

  • 确定同步哪些数据
  • App Services 配置
  • 客户端Realm 数据模型和配置
  • 写入同步 Realm
  • 成功写入
  • 补偿写入
  • 与查询订阅不匹配的写入
  • 与权限不匹配的写入

使用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 数据模型和订阅的客户端应用程序。

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
}

本页中的示例使用以下模式:

@RealmModel()
class _Car {
@MapTo("_id")
@PrimaryKey()
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();

对 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 会在称为补偿写入的非致命错误操作中恢复写入。 在以下情况下可能会进行补偿写入:

  • 写入与查询订阅不匹配:写入的对象与用户的权限匹配,但与查询订阅不匹配。

  • 写入与权限不匹配:写入的对象与查询订阅匹配,但与用户的权限不匹配。

更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:

  1. 由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。

  2. 同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。

  3. 服务器向客户端发回恢复操作(称为“补偿写入”)。

  4. 客户端 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 日志中的错误消息为:

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 日志中的错误消息提供了一些附加信息,可帮助你确定这是权限问题,而不是查询订阅问题。 在此示例中,错误消息显示对象与用户角色不匹配:

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\""
}
}

后退

管理订阅