将数据写入同步 Realm - C++ SDK
在此页面上
使用 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)
App Services 配置
根据上述示例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 数据模型和配置
本页上的示例将以下同步 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。
写入同步 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对象的数组。
更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:
由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。
同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。
服务器向客户端发回恢复操作(称为“补偿写入”)。
客户端 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 而不是每次更改来提高同步性能。