Manage Flexible Sync Subscriptions- .NET SDK
On this page
- Manage Your Subscriptions
- Add a Subscription
- Bootstrap the Realm with Initial Subscriptions
- Add a Subscription to the Existing Subscription Set
- Batching Multiple Subscriptions
- Subscription Options
- Update Subscriptions with a New Query
- Remove Subscriptions
- Remove a Subscription by Query
- Remove a Subscription by Name
- Remove All Subscriptions of a Class Name or Object Type
- Remove All Subscriptions
- Wait for Subscription Changes to Sync
- Subscription State
- Flexible Sync RQL Requirements and Limitations
- Indexed Queryable Fields Subscription Requirements
- Unsupported Query Operators in Flexible Sync
- List Queries
- Embedded or Linked Objects
Flexible Sync uses subscriptions and permissions to determine which data to sync with your App.
To use Flexible Sync in a .NET application:
Authenticate a user in your client project.
You can add, update, and remove query subscriptions to determine which data syncs to the client device.
Note
Flexible Sync Prerequisites
Enabling Flexible Sync in your App requires a non-sharded Atlas cluster running MongoDB 5.0 or greater
Note
Realm .NET SDK Version Requirement
In addition to the requirements above, you must use Realm .NET version 10.9.0 or greater in order to use Flexible Sync in your .NET client application.
Important
Flexible Sync does not support all the operators available in Realm Query Language. See Flexible Sync RQL Limitations for details.
Manage Your Subscriptions
When configuring Flexible Sync on the backend, you specify which fields your client application can query. In the client application, use the subscriptions API to manage a set of subscriptions to specific queries on queryable fields.
You can:
Get a list of all subscriptions
Add subscriptions
Check subscription state
Update a subscription with a new query
Remove individual subscriptions or all subscriptions of a type
When the data matches the subscription, and the authenticated user has the appropriate permissions, Atlas App Services syncs the backend data with the client app.
You can specify a string name for your subscription. If you do not give your subscription a name, the name is set to null.
When you create a subscription, App Services looks for data matching a query on a specific object type. In your Flexible Sync subscriptions, you can have subscriptions on several different object types or several queries on the same object type.
You can't create subscriptions for asymmetric objects because they only send data to your App Services backend.
Changed in version 11.6.1: Geospatial data supported in Atlas Device Sync
In the Realm .NET version 11.6.1 and later, you can create subscriptions to geospatial queries. If you try to subscribe to a geospatial query with an older version of the SDK, you will receive a server error with a compensating write.
For more information, refer to Query Geospatial Data.
Add a Subscription
You must have at least one subscription before you can read from or write to the realm. You should create one or more initial subscriptions when you configure Flexible Sync, rather than adding subscriptions after initialization.
Bootstrap the Realm with Initial Subscriptions
You can bootstrap a realm with an initial subscription set when you open it with a FlexibleSyncConfiguration. Set the PopulateInitialSubscriptions parameter to a callback that is invoked when the realm is created. Add the queries you want to use to bootstrap the realm, as shown in the following example:
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);
Add a Subscription to the Existing Subscription Set
You should initialize a realm with your SubscriptionSet. You can then access the subscription set with the Realm.Subscriptions property if you need to add additional queries or update existing subscriptions.
To add a subscription to an existing subscription set, create the query and then call 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" });
The SubscribeAsync
method is shorthand for using SubscriptionSet.Update()
to create an update block, and then calling the SubscriptionSet.Add()
method on the SubscriptionSet
.
Batching Multiple Subscriptions
While you can add a single query using SubscribeAsync
, you can only batch
multiple queries within a SubscriptionSet.Update
block. Performing query
updates is an expensive operation on the server. We strongly advise designing
your application to minimize updates. You can do this by creating all
subscriptions in a single update block the first time the user launches the app
and batching any follow-up changes to the subscription set.
In the example below, we subscribe to three queries.
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 }); });
Subscription Options
Note that both methods for adding a subscription provide an overload for passing
in a SubscriptionOptions
object. SubscriptionOptions
contains configuration options for your
subscription:
a
Name
string fieldan
UpdateExisting
boolean field.
If UpdateExisting
is true, adding a subscription with an
existing name will replace the existing query with the new query. This is the
default behavior. However, if you set UpdateExisting
to false, and try to
replace a named subscription with a different query, Realm throws an exception.
Realm always ignores duplicate subscriptions, whether named or unnamed.
Important
Subscribe to Linked Objects
You must add both an object and its linked object to the subscription set to see a linked object.
If your subscription results contain an object with a property that links to an object not contained in the results, the link appears to be nil. There is no way to distinguish whether that property's value is legitimately nil, or whether the object it links to exists but is out of view of the query subscription.
Update Subscriptions with a New Query
You can update a named subscription with a new query. To update a subscription's
query, pass the new query and a subscription option with the name of the
subscription that you want to update to the
SubscriptionSet.Add()
method. Like adding a new subscription,
you must update a subscription within an update block by calling
SubscriptionSet.Update()
method.
Note
Updating an unnamed subscription is not possible. Alternatively, you can delete the unnamed subscription, and create a new subscription with the desired query.
In the following example, long running tasks are re-defined to be any tasks that have taken more than 130 minutes:
realm.Subscriptions.Update(() => { var updatedLongRunningTasksQuery = realm.All<Item>() .Where(t => t.Status == "completed" && t.ProgressMinutes > 130); realm.Subscriptions.Add(updatedLongRunningTasksQuery, new SubscriptionOptions() { Name = "longRunningTasks" }); });
Note
Attempting to update a subscription that has the SubscriptionOptions.UpdateExisting
field set to false will throw an exception.
Remove Subscriptions
To remove subscriptions from the subscription set, you can:
Remove a single subscription with the given query
Remove a single subscription with the given name
Remove a single subscription with the given subscription
Remove all subscriptions of a specific type
Remove all subscriptions
When you remove a subscription query, the server also removes synced data from the client device.
Remove a Subscription by Query
Within an update block, you can remove a specific subscription by query. Pass the
query to the Remove()
method on the SubscriptionSet
.
In the following example, the subscription to tasks with an owner named 'Ben' is removed from the subscriptions set.
realm.Subscriptions.Update(() => { // remove a subscription by it's query var query = realm.All<Item>().Where(i => i.Owner == "Ben"); realm.Subscriptions.Remove(query); });
Remove a Subscription by Name
Within an update block, you can remove a specific subscription by name. Pass the
name to the Remove()
method on the SubscriptionSet
.
realm.Subscriptions.Update(() => { // remove a named subscription var subscriptionName = "longRunningItemsSubscription"; realm.Subscriptions.Remove(subscriptionName); });
Remove All Subscriptions of a Class Name or Object Type
Within an update block, you can remove remove all unnamed subscriptions of a
class by passing the class name as a string to the
RemoveAll("ClassName")
method. The RemoveAll()
method has an optional second argument that is a
boolean, removedName
, which also removes the named subscriptions if it is
set to true
. removedName
is set to false by default.
Alternatively, you can remove all unnamed subscriptions of an object type
with RemoveAll().
The RemoveAll<Type>()
method has an optional boolean removedName
argument
that also removes the named subscriptions if it is set to true
. The removedName
argument is set to false by default.
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>(); });
Remove All Subscriptions
Within an update block, you can remove all unnamed subscriptions from the
subscriptions set. Call the RemoveAll()
method on the SubscriptionSet
. The RemoveAll()
method has an optional
boolean removedName
argument which also removes the named subscriptions if
it is set to true
. removedName
is set to false by default.
realm.Subscriptions.Update(() => { // remove all subscriptions, including named subscriptions realm.Subscriptions.RemoveAll(true); });
Wait for Subscription Changes to Sync
Mutating the subscription set within an update block is only one part of changing a subscription. After the local subscription change, the realm synchronizes with the server to resolve any updates to the data due to the subscription change. This could mean adding or removing data from the synced realm.
Use the SubscriptionSet.WaitForSynchronizationAsync() method to wait for the server to acknowledge this set of subscriptions. If the server rejects the change, the SubscriptionSetState will be an error state, and an exception will be thrown.
An exception may occur if:
An unsupported query is subscribed to. Subscribing to an unsupported query will pause synchronization. To resume synchronization, remove the unsupported query.
You are performing an invalid action, such as adding an object that does not match a subscription. This triggers a client reset: data is erased from the realm, and a new copy of the data is created without any subscriptions in the set.
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}"); }
Subscription State
Use the SubscriptionSet.State property to read the current state of the subscription set.
The Superseded
state is a SubscriptionSetState that can occur when another
thread updates a subscription on a different instance of the subscription set.
If the state becomes Superseded
, you must obtain a new instance of the
subscription set before you can update it.
Note
Subscription State "Complete"
The subscription set state "complete" does not mean "sync is done" or "all documents have been synced". "Complete" means the following two things have happened:
The subscription has become the active subscription set that is currently being synchronized with the server.
The documents that matched the subscription at the time the subscription was sent to the server are now on the local device. Note that this does not necessarily include all documents that currently match the subscription.
The Realm SDK does not provide a way to check whether all documents that match a subscription have synced to the device.
Flexible Sync RQL Requirements and Limitations
Indexed Queryable Fields Subscription Requirements
Adding an indexed queryable field to
your App can improve performance for simple queries on data that is strongly
partitioned. For example, an app where queries strongly map data to a device,
store, or user, such as user_id == $0, “641374b03725038381d2e1fb”
, is
a good candidate for an indexed queryable field. However, an indexed
queryable field has specific requirements for use in a query subscription:
The indexed queryable field must be used in every subscription query. It cannot be missing from the query.
The indexed queryable field must use an
==
orIN
comparison against a constant at least once in the subscription query. For example,user_id == $0, "641374b03725038381d2e1fb"
orstore_id IN $0, {1,2,3}
.
You can optionally include an AND
comparison as long as the indexed
queryable field is directly compared against a constant using ==
or IN
at least once. For example, store_id IN {1,2,3} AND region=="Northeast"
or store_id == 1 AND (active_promotions < 5 OR num_employees < 10)
.
Invalid Flexible Sync queries on an indexed queryable field include queries where:
The indexed queryable field does not use
AND
with the rest of the query. For examplestore_id IN {1,2,3} OR region=="Northeast"
is invalid because it usesOR
instead ofAND
. Similarly,store_id == 1 AND active_promotions < 5 OR num_employees < 10
is invalid because theAND
only applies to the term next to it, not the entire query.The indexed queryable field is not used in an equality operator. For example
store_id > 2 AND region=="Northeast"
is invalid because it uses only the>
operator with the indexed queryable field and does not have an equality comparison.The query is missing the indexed queryable field entirely. For example,
region=="Northeast
ortruepredicate
are invalid because they do not contain the indexed queryable field.
Unsupported Query Operators in Flexible Sync
Flexible Sync has some limitations when using RQL operators. When you write the query subscription that determines which data to sync, the server does not support these query operators. However, you can still use the full range of RQL features to query the synced data set in the client application.
Operator Type | Unsupported Operators |
---|---|
Aggregate Operators | @avg , @count , @max , @min , @sum |
Query Suffixes | DISTINCT , SORT , LIMIT |
Case insensitive queries ([c]
) cannot use indexes effectively.
As a result, case insensitive queries are not recommended, since they could lead to
performance problems.
Flexible Sync only supports @count
for array fields.
List Queries
Flexible Sync supports querying lists using the IN
operator.
You can query a list of constants to see if it contains the value of a queryable field:
// Query a constant list for a queryable field value "priority IN { 1, 2, 3 }"
If a queryable field has an array value, you can query to see if it contains a constant value:
// Query an array-valued queryable field for a constant value "'comedy' IN genres"
Warning
You cannot compare two lists with each other in a Flexible Sync query. Note that this is valid Realm Query Language syntax outside of Flexible Sync queries.
// 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"
Embedded or Linked Objects
Flexible Sync does not support querying on properties in Embedded Objects
or links. For example, obj1.field == "foo"
.