托管 Flexible Sync 订阅 - .NET SDK
在此页面上
Flexible Sync 使用订阅和权限来确定要与应用同步哪些数据。
要在 .NET 应用程序中使用 Flexible Sync,请执行以下操作:
您可以添加、更新和删除查询订阅,以确定哪些数据同步到客户端设备。
重要
Flexible Sync不支持 RQL 中提供的所有操作符。 有关详细信息,请参阅“ Flexible Sync RQL 限制”。
托管您的订阅
在后端配置 Flexible Sync 时,您可以指定客户端应用程序可以查询哪些字段。 在客户端应用程序中,使用订阅API 管理对可查询字段的特定查询的一组订阅。
您可以:
获取所有订阅的列表
添加订阅
检查订阅状态
使用新查询更新订阅
删除单个订阅或某一类型的所有订阅
当数据与订阅匹配,并且经过身份验证的用户具有适当的权限时,Atlas App Services 会将后端数据与客户端应用程序同步。
您可以为订阅指定字符串名称。 如果没有为订阅命名,则该名称将设置为 null。
创建订阅时,App Services 会查找与特定对象类型的查询匹配的数据。 在Flexible Sync订阅中,您可以对几种不同的Realm 对象类型进行订阅,也可以对同一Realm 对象类型进行多次查询。
您无法为非对称对象创建订阅,因为它们仅将数据发送到 App Services 后端。
在版本 11.6.1 中进行了更改: Atlas Device Sync 支持的地理空间数据
在 Realm .NET 11.6.1 及更高版本中,您可以创建地理空间查询的订阅。 如果您尝试使用旧版本的 SDK 订阅地理空间查询,您将收到带有补偿写入的服务器错误。
有关详细信息,请参阅查询地理空间数据。
添加订阅
您必须至少有一个订阅,才能读取或写入 Realm。 您应该在配置Flexible Sync时创建一个或多个初始订阅,而不是在初始化后添加订阅。
通过初始订阅引导 Realm
当您使用FlexibleSyncConfiguration打开 Realm 时,可以使用初始订阅集来引导 Realm。将PopulateInitialSubscriptions参数设置为创建 Realm 时调用的回调。 添加要用于引导 Realm 的查询,如以下示例所示:
var config = new FlexibleSyncConfiguration(app.CurrentUser) { PopulateInitialSubscriptions = (realm) => { var myItems = realm.All<Item>().Where(n => n.OwnerId == myUserId); realm.Subscriptions.Add(myItems); } }; // The process will complete when all the user's items have been downloaded. var realm = await Realm.GetInstanceAsync(config);
将订阅添加到现有订阅集
您应该使用SubscriptionSet初始化一个 Realm。 如果需要添加其他查询或更新现有订阅,则可以使用Realm.Subscriptions属性访问订阅集。
要将订阅添加到现有订阅集,请创建查询,然后调用IQueryable.SubscribeAsync():
var query = realm.All<Team>().Where(t => t.Name == "MyTeam"); await query.SubscribeAsync(); // you can also pass a SubscriptionOptions object: var query2 = realm.All<Team>().Where(t => t.Name == "DevelopmentTeam"); await query2.SubscribeAsync( new SubscriptionOptions() { Name = "devTeamSubscription" });
SubscribeAsync
方法是使用SubscriptionSet.Update()创建更新区块,然后对SubscriptionSet
调用SubscriptionSet.Add()方法的简写。
批处理多个订阅
虽然可以使用SubscribeAsync
添加单个查询,但只能在SubscriptionSet.Update
区块内批处理多个查询。 在服务器上执行查询更新是一项成本高昂的操作。 我们强烈建议您在设计应用程序时尽量减少更新。 为此,您可以在用户首次启动应用时在单个更新区块中创建所有订阅,并批处理对订阅集进行任何后续更改。
在下面的示例中,我们订阅了三个查询。
realm.Subscriptions.Update(() => { // Subscribe to all long running items, and name // the subscription "longRunningItems" var longRunningTasksQuery = realm.All<Item>() .Where(t => t.ProgressMinutes > 120); realm.Subscriptions.Add(longRunningTasksQuery, new SubscriptionOptions() { Name = "longRunningItems" }); // Subscribe to all of Ben's Items realm.Subscriptions.Add(realm.All<Item>() .Where(t => t.Owner == "Ben")); // Subscribe to all Teams, name the subscription // 'teamsSubscription', and throw an error if // this subscription name already exists. realm.Subscriptions.Add(realm.All<Team>(), new SubscriptionOptions() { Name = "teams", UpdateExisting = false }); });
SubscriptionOptions
请注意,这两种添加订阅的方法都提供了用于传入SubscriptionOptions对象的重载。 SubscriptionOptions
包含订阅的配置选项:
Name
字符串字段一个
UpdateExisting
布尔字段。
如果UpdateExisting
为 true,则添加具有现有名称的订阅会将现有查询替换为新查询。 这是默认行为。 但是,如果您将UpdateExisting
设置为 false,并尝试用不同的查询替换命名订阅,Realm 会抛出异常。
Realm 始终会忽略重复的订阅,无论是已命名还是未命名。
重要
订阅链接对象
要查看链接对象,必须将对象及其链接对象都添加到订阅集中。
如果订阅结果包含一个对象,该对象的属性链接到结果中未包含的对象,则该链接显示为空。我们无法区分该属性的值是否为合法零值,或者所链接的对象是否存在但不在查询订阅的视图中。
使用新查询更新订阅
您可以使用新查询更新命名订阅。 要更新订阅的查询,请将新查询和带有要更新的订阅名称的订阅选项传递给SubscriptionSet.Add()
方法。 与添加新订阅一样,您必须通过调用SubscriptionSet.Update()
方法在更新区块内更新订阅。
在以下示例中,长时间运行的任务被重新定义为任何耗时超过 130 分钟的任务:
realm.Subscriptions.Update(() => { var updatedLongRunningTasksQuery = realm.All<Item>() .Where(t => t.Status == "completed" && t.ProgressMinutes > 130); realm.Subscriptions.Add(updatedLongRunningTasksQuery, new SubscriptionOptions() { Name = "longRunningTasks" }); });
注意
尝试更新将SubscriptionOptions.UpdateExisting
字段设置为 false 的订阅会引发异常。
删除订阅
要从订阅集中删除订阅,您可以:
使用给定查询删除单个订阅
删除具有给定名称的单个订阅
删除具有给定订阅的单个订阅
删除特定类型的所有订阅
删除所有订阅
删除订阅查询时,服务器也会删除客户端设备上的同步数据。
通过查询删除订阅
在更新区块内,您可以通过查询删除特定订阅。 将查询传递给SubscriptionSet
上的Remove()方法。
在以下示例中,将从订阅集中删除所有者名为“Ben”的任务的订阅。
realm.Subscriptions.Update(() => { // remove a subscription by it's query var query = realm.All<Item>().Where(i => i.Owner == "Ben"); realm.Subscriptions.Remove(query); });
按名称删除订阅
在更新区块中,您可以按名称删除特定订阅。 将该名称传递给SubscriptionSet
上的Remove()方法。
realm.Subscriptions.Update(() => { // remove a named subscription var subscriptionName = "longRunningItemsSubscription"; realm.Subscriptions.Remove(subscriptionName); });
删除类名称或Realm 对象类型的所有订阅
在更新区块中,您可以通过将类名称作为string传递给RemoveAll("ClassName")方法来删除类的所有未命名订阅。 RemoveAll()
方法有一个可选的第二个参数,它是一个布尔值removedName
,如果它设置为true
,它也会删除命名订阅。 默认情况下, removedName
设置为 false。
或者,您可以使用RemoveAll()删除对象类型的所有未命名订阅。 RemoveAll<Type>()
方法有一个可选的布尔值removedName
参数,如果设置为true
,该参数也会删除命名订阅。 默认情况下, removedName
参数设置为 false。
realm.Subscriptions.Update(() => { // remove all subscriptions of the "Team" Class Name realm.Subscriptions.RemoveAll("Team"); // Alernatively, remove all subscriptions of the "Team" object type realm.Subscriptions.RemoveAll<Team>(); });
删除所有订阅
在更新区块中,您可以从订阅集中删除所有未命名的订阅。 对SubscriptionSet
调用RemoveAll()方法。 RemoveAll()
方法有一个可选的布尔值removedName
参数,如果设置为true
,该参数也会删除命名订阅。 默认情况下, removedName
设置为 false。
realm.Subscriptions.Update(() => { // remove all subscriptions, including named subscriptions realm.Subscriptions.RemoveAll(true); });
等待订阅更改同步
在更新区块内更改订阅集只是更改订阅的一部分。 本地订阅更改后,域会与服务器同步,以解决由于订阅更改而导致的任何数据更新问题。这可能意味着在同步 Realm 中添加或删除数据。
使用SubscriptionSet.WaitForSynchronizationAsync()方法等待服务器确认这组订阅。 如果服务器拒绝更改,则SubscriptionSetState将处于错误状态,并引发异常。
如果出现以下情况,则可能会出现异常:
订阅了不支持的查询。 订阅不支持的查询将暂停同步。 要恢复同步,请删除不支持的查询。
您正在执行无效的操作,例如添加与订阅不匹配的对象。 此Atlas Triggers客户端重置:从 Realm 中删除数据,并创建数据的新副本,且集合中不含任何订阅。
try { await realm.Subscriptions.WaitForSynchronizationAsync(); } catch (SubscriptionException ex) { // do something in response to the exception or log it Console.WriteLine($@"The subscription set's state is Error and synchronization is paused: {ex.Message}"); }
订阅状态
使用 SubscriptionSet.state 属性读取订阅集的当前状态。
Superseded
状态是一种SubscriptionSetState ,当另一个线程在订阅集的不同实例上更新订阅时,可能会出现该状态。 如果状态变为Superseded
,则必须先获取订阅集的新实例,然后才能进行更新。
注意
订阅状态 Complete(完成)
订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:
该订阅已成为当前正在与服务器同步的活动订阅集。
在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。
对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。
“灵活同步”RQL 的要求和限制
已索引可查询字段订阅的要求
向应用添加索引可查询字段可以提高对强分区数据进行简单查询的性能。 例如,如果查询将数据强映射到设备、商店或用户的应用(例如user_id == $0, “641374b03725038381d2e1fb”
,则非常适合使用索引可查询字段。 但是,在查询订阅中使用索引化可查询字段有特定的要求:
每个订阅查询中都必须使用索引化可查询字段。查询中不能缺少该字段。
在订阅查询中,索引化可查询字段必须使用
==
或IN
进行至少一次针对常量的比较。例如,user_id == $0, "641374b03725038381d2e1fb"
或store_id IN $0, {1,2,3}
。
可以选择包含 AND
比较,前提是使用 ==
或 IN
将索引化可查询字段直接与常量进行至少一次比较。例如,store_id IN {1,2,3} AND region=="Northeast"
或 store_id == 1 AND (active_promotions < 5 OR num_employees < 10)
。
对索引化可查询字段的无效灵活同步查询包括以下情况的查询:
索引化可查询字段未将
AND
与查询的其余部分结合使用。例如,store_id IN {1,2,3} OR region=="Northeast"
是无效的,因为它使用了OR
而不是AND
。同样,store_id == 1 AND active_promotions < 5 OR num_employees < 10
也是无效的,因为AND
仅适用于其旁边的词,而不适用于整个查询。索引化可查询字段未在相等运算符中使用。例如,
store_id > 2 AND region=="Northeast"
是无效的,因为它仅将>
运算符与索引化可查询字段结合使用,而没有相等比较。查询中完全没有索引化可查询字段。例如,
region=="Northeast
和truepredicate
都是无效的,因为它们不含索引化可查询字段。
灵活同步中不支持的查询运算符
使用 RQL 操作符时,Flexible Sync有一些限制。当您编写确定要同步哪些数据的查询订阅时,服务器不支持这些查询操作符。但是,您仍然可以使用全部的 RQL 功能来查询客户端应用中的同步数据集。
运算符类型 | 不支持的运算符 |
---|---|
聚合操作符 | @avg , @count , @max , @min , @sum |
查询后缀 | DISTINCT , SORT , LIMIT |
不区分大小写的查询 ([c]
) 无法有效地使用索引。因此,不建议使用不区分大小写的查询,因为它们可能会导致性能问题。
灵活同步仅支持数组字段的 @count
。
列出查询
灵活同步支持使用 IN
运算符来查询列表。
可以查询常量列表,查看其中是否包含可查询字段的值:
// Query a constant list for a queryable field value "priority IN { 1, 2, 3 }"
如果某个可查询字段具有数组值,则可以通过查询确定其中是否包含常量值:
// Query an array-valued queryable field for a constant value "'comedy' IN genres"
警告
您无法在灵活同步查询中相互比较两个列表。请注意,这是灵活同步查询之外的有效 Realm 查询语言语法。
// Invalid Flexible Sync query. Do not do this! "{'comedy', 'horror', 'suspense'} IN genres" // Another invalid Flexible Sync query. Do not do this! "ANY {'comedy', 'horror', 'suspense'} != ANY genres"
嵌入式对象或关联对象
灵活同步不支持查询嵌入式对象或链接中的属性。例如,obj1.field == "foo"
。
查询大小限制
订阅设立任何给定查询订阅的大小限制为256 kB 。 超过此限制会导致LimitsExceeded 错误。