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

将数据写入同步 Realm - C++ SDK

在此页面上

  • 确定同步哪些数据
  • App Services 配置
  • 客户端Realm 数据模型和配置
  • 写入同步 Realm
  • 成功写入
  • 补偿写入
  • 写入与查询订阅不匹配
  • 写入与权限不匹配
  • 补偿写入错误信息
  • 对写入进行分组以提高性能

使用 Flexible Sync将数据写入同步域时,您可以使用与对非同步域执行CRUD改查操作时相同的 API。 但是,在开发应用程序时,需要记住一些行为差异。

当您写入同步 Realm 时,写入操作必须匹配以下两项

  • 同步订阅查询

  • App Services App 中的权限

如果您尝试写入与查询订阅或用户权限不匹配的数据,Realm 会使用称为补偿写入的非致命错误操作来恢复写入。

要了解有关为应用程序配置权限的更多信息,请参阅 Device SyncAtlas App Services文档中 的基于角色的权限 和 权限指南 。

要了解有关权限被拒绝错误、补偿写入错误和其他Device Sync 错误类型的更多信息,请参阅 Atlas App Services文档中的 同步错误 。

您可以写入同步 Realm 的数据由以下因素决定:

  • Device Sync 配置

  • 应用中的权限

  • 打开 Realm 时使用的 Flexible Sync 订阅查询

本页上的示例使用具有以下 Device Sync 配置的 Atlas App Services App 以及具有以下 Realm 软件开发工具包(Realm SDK) Realm 数据模型和订阅的客户端应用程序。

在此示例中,客户端应用程序使用以下对象模型:

struct Item {
realm::primary_key<realm::object_id> _id{realm::object_id::generate()};
std::string ownerId;
std::string itemName;
int64_t complexity;
};
REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)

根据上述示例Realm 对象模型,Device Sync 配置有以下可查询字段:

  • _id (始终包含在内)

  • complexity

  • ownerId

App Services App 的权限配置为仅允许用户读取和写入自己的数据:

{
"roles": [
{
"name": "readOwnWriteOwn",
"apply_when": {},
"document_filters": {
"write": {
"ownerId": "%%user.id"
},
"read": {
"ownerId": "%%user.id"
}
},
"read": true,
"write": true,
"insert": true,
"delete": true,
"search": true
}
]
}

在Atlas collection中的任何对象,如果ownerId与登录用户的user.identifier()不匹配,则无法同步到此 域。

本页上的示例将以下同步 Realm 配置与此同步查询和对象模型结合使用:

auto appConfig = realm::App::configuration();
appConfig.app_id = APP_ID;
auto app = realm::App(appConfig);
auto user = app.login(realm::App::credentials::anonymous()).get();
auto dbConfig = user.flexible_sync_configuration();
auto syncRealm = realm::db(dbConfig);
// Add subscription
auto subscriptionUpdateSuccess =
syncRealm.subscriptions()
.update([](realm::mutable_sync_subscription_set &subs) {
// Get Items from Atlas that match this query.
// Uses the queryable field `complexity`.
// Sync Item objects with complexity less than or equal to 4.
subs.add<realm::Item>(
"simple items", [](auto &obj) { return obj.complexity <= 4; });
})
.get();
struct Item {
realm::primary_key<realm::object_id> _id{realm::object_id::generate()};
std::string ownerId;
std::string itemName;
int64_t complexity;
};
REALM_SCHEMA(Item, _id, ownerId, itemName, complexity)

使用此同步查询,Atlas 集合中complexity属性值大于4的任何对象都无法同步到此 Realm。

对Flexible Sync Realm的写入大致可以分为以下两类Flexible Sync

  • 成功写入:写入的对象同时与查询订阅和用户权限匹配。 该对象成功写入到域,并成功同步到 App Services 后端和其他设备。

  • 补偿写入:写入的对象与订阅查询不匹配,或者用户没有足够的权限来执行写入。 Realm 通过补偿写入操作来恢复非法写入。

提示

如果要写入与查询订阅不匹配的对象,可以打开该对象与查询订阅匹配的另一个域。或者,您可以将对象写入不强制执行权限或订阅查询的非同步 Realm。

当写入与用户权限客户端中的查询订阅匹配时,Realm C++ SDK 可以成功将对象写入同步 Realm。 当设备具有网络连接时,此对象与 App Services 后端同步。

// Per the Device Sync permissions, users can only read and write data
// where the `Item.ownerId` property matches their own user ID.
auto simpleItem =
realm::Item{.ownerId = user.identifier(),
.itemName = "This item meets sync criteria",
.complexity = 3};
// `simpleItem` successfully writes to the realm and syncs to Atlas
// because its data matches the subscription query (complexity <= 4)
// and its `ownerId` field matches the user ID.
syncRealm.write([&] { syncRealm.add(std::move(simpleItem)); });

当写入与查询订阅用户权限不匹配时,Realm 会恢复写入并提供compensating_write_error_info对象的数组。

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

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

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

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

  4. 客户端 Realm 恢复非法写入操作。

在对给定对象的非法写入和相应的补偿写入之间,客户端对该对象的任何写入都将丢失。 实际上,这可能看起来像写入成功,但当 Realm 与 App Services 后端同步并执行补偿写入时,该对象“消失”。

出现这种情况时,您可以参阅Atlas App Services日志或使用客户端中的 compensating_writes_info() 函数来获取有关错误的其他信息。 有关更多信息,请参阅本页的“补偿写入错误信息”部分。

根据上面详细介绍的 Flexible Sync Realm配置,尝试写入此对象会导致补偿写入错误,因为该对象与查询订阅不匹配:

// The complexity of this item is `7`. This is outside the bounds
// of the subscription query, which triggers a compensating write.
auto complexItem =
realm::Item{._id = primaryKey,
.ownerId = user.identifier(),
.itemName = "Test compensating writes",
.complexity = 7};
// This should trigger a compensating write error when it tries to sync
// due to server-side permissions, which gets logged with the error handler.
syncRealm.write([&] { syncRealm.add(std::move(complexItem)); });
Connection[2]: Session[10]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)

您将在 App Services 日志中看到以下错误消息:

Error:
Client attempted a write that is not allowed; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"ObjectID(\"6557ddb0bf050934870ca0f5\")": "write to ObjectID(\"6557ddb0bf050934870ca0f5\")
in table \"Item\" not allowed; object is outside of
the current query view"
}
}

给定上述 Device Sync 配置中的权限,尝试写入此对象会导致补偿写入错误,因为ownerId属性与登录用户的user.identifier()不匹配:

// The `ownerId` of this item does not match the user ID of the logged-in
// user. The user does not have permissions to make this write, which
// triggers a compensating write.
auto itemWithWrongOwner = realm::Item{
.ownerId = "not the current user",
.itemName = "Trigger an incorrect permissions compensating write",
.complexity = 1};
syncRealm.write([&] { syncRealm.add(std::move(itemWithWrongOwner)); });
Connection[2]: Session[11]: Received: ERROR "Client attempted a write that is not allowed; it has been reverted" (error_code=231, is_fatal=false, error_action=Warning)

您将在 App Services 日志中看到以下错误消息:

Error:
Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"ObjectID(\"6557ddbabf050934870ca0f8\")": "write to ObjectID(\"6557ddbabf050934870ca0f8\")
in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\""
}
}

您可以在客户端中使用compensating_writes_info()函数获取有关为何会发生补偿写入的其他信息,该函数提供了一个compensating_write_error_info结构体数组,其中包含:

  • 客户端尝试写入的对象的object_name

  • 特定对象的primary_key

  • 用于补偿写入错误的reason

此信息与您在Atlas App Services日志中找到的信息相同。 出于方便和调试目的,C++ SDK 在客户端公开此对象。

以下示例说明了如何记录有关补偿写入错误的信息:

auto info = receivedSyncError.compensating_writes_info();
for (auto &v : info) {
std::cout << "A write was rejected with a compensating write error.\n";
std::cout << "An object of type " << v.object_name << "\n";
std::cout << "was rejected because " << v.reason << ".\n";
}
A write was rejected with a compensating write error.
An object of type Item
was rejected because write to ObjectID("6557ddb0bf050934870ca0f5") in
table "Item" not allowed; object is outside of the current query view.
  • 此消息中的Item是此页面上的对象模型中使用的Item对象。

  • table "Item"指的是此对象将同步的 Atlas collection。

  • 此消息中object is outside of the current query view的原因是查询订阅设置为要求对象的complexity属性小于或等于4 。 客户端尝试在此边界之外写入对象。

  • 主键是客户端尝试写入的特定对象的objectId

订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。

后退

托管同步订阅