• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-dotnet / 9742975006

01 Jul 2024 10:58AM UTC coverage: 81.384% (+0.03%) from 81.358%
9742975006

Pull #3635

github

7b5d83
nirinchev
Implement .As<T>, add some notification tests
Pull Request #3635: Add flexible schema mapping POC

2317 of 2997 branches covered (77.31%)

Branch coverage included in aggregate %.

12 of 15 new or added lines in 2 files covered. (80.0%)

7 existing lines in 1 file now uncovered.

6846 of 8262 relevant lines covered (82.86%)

42528.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.48
/Realm/Realm/Extensions/CollectionExtensions.cs
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
using System;
20
using System.Collections.Generic;
21
using System.Collections.Specialized;
22
using System.ComponentModel;
23
using System.Diagnostics.CodeAnalysis;
24
using System.Linq;
25
using System.Threading;
26
using System.Threading.Tasks;
27
using Realms.Helpers;
28
using Realms.Sync;
29

30
namespace Realms;
31

32
/// <summary>
33
/// A set of extensions methods exposing notification-related functionality over collections.
34
/// </summary>
35
[EditorBrowsable(EditorBrowsableState.Never)]
36
public static class CollectionExtensions
37
{
38
    /// <summary>
39
    /// A convenience method that casts <see cref="IQueryable{T}"/> to <see cref="IRealmCollection{T}"/> which
40
    /// implements <see cref="INotifyCollectionChanged"/>.
41
    /// </summary>
42
    /// <param name="query">The <see cref="IQueryable{T}"/> to observe for changes.</param>
43
    /// <typeparam name="T">Type of the <see cref="RealmObject"/> or <see cref="EmbeddedObject"/> in the results.</typeparam>
44
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
45
    /// <returns>The collection, implementing <see cref="INotifyCollectionChanged"/>.</returns>
46
    public static IRealmCollection<T> AsRealmCollection<T>(this IQueryable<T> query)
47
        where T : IRealmObjectBase?
48
    {
49
        Argument.NotNull(query, nameof(query));
75✔
50

51
        return Argument.EnsureType<IRealmCollection<T>>(query, $"{nameof(query)} must be an instance of IRealmCollection<{typeof(T).Name}>.", nameof(query));
75✔
52
    }
53

54
    /// <summary>
55
    /// A convenience method that casts <see cref="IQueryable{T}"/> to <see cref="IRealmCollection{T}"/> and subscribes for change notifications.
56
    /// </summary>
57
    /// <param name="results">The <see cref="IQueryable{T}"/> to observe for changes.</param>
58
    /// <typeparam name="T">Type of the <see cref="RealmObject"/> or <see cref="EmbeddedObject"/> in the results.</typeparam>
59
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
60
    /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
61
    /// <param name="keyPathsCollection">An optional <see cref="KeyPathsCollection"/>, that indicates which changes in properties should raise a notification.</param>
62
    /// <returns>
63
    /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
64
    /// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
65
    /// </returns>
66
    public static IDisposable SubscribeForNotifications<T>(this IQueryable<T> results, NotificationCallbackDelegate<T> callback,
67
        KeyPathsCollection? keyPathsCollection = null)
68
        where T : IRealmObjectBase?
69
    {
70
        return results.AsRealmCollection().SubscribeForNotifications(callback, keyPathsCollection);
32✔
71
    }
72

73
    /// <summary>
74
    /// A convenience method that casts <see cref="ISet{T}"/> to <see cref="IRealmCollection{T}"/> which implements
75
    /// <see cref="INotifyCollectionChanged"/>.
76
    /// </summary>
77
    /// <param name="set">The <see cref="ISet{T}"/> to observe for changes.</param>
78
    /// <typeparam name="T">Type of the elements in the set.</typeparam>
79
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
80
    /// <returns>The collection, implementing <see cref="INotifyCollectionChanged"/>.</returns>
81
    public static IRealmCollection<T> AsRealmCollection<T>(this ISet<T> set)
82
    {
83
        Argument.NotNull(set, nameof(set));
844✔
84

85
        return Argument.EnsureType<IRealmCollection<T>>(set, $"{nameof(set)} must be an instance of IRealmCollection<{typeof(T).Name}>.", nameof(set));
844✔
86
    }
87

88
    /// <summary>
89
    /// A convenience method that casts <see cref="ISet{T}"/> to <see cref="IRealmCollection{T}"/> and subscribes for change notifications.
90
    /// </summary>
91
    /// <param name="set">The <see cref="ISet{T}"/> to observe for changes.</param>
92
    /// <typeparam name="T">Type of the elements in the set.</typeparam>
93
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
94
    /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
95
    /// <param name="keyPathsCollection">An optional <see cref="KeyPathsCollection"/>, that indicates which changes in properties should raise a notification.</param>
96
    /// <returns>
97
    /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
98
    /// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
99
    /// </returns>
100
    public static IDisposable SubscribeForNotifications<T>(this ISet<T> set, NotificationCallbackDelegate<T> callback,
101
        KeyPathsCollection? keyPathsCollection = null)
102
        => set.AsRealmCollection().SubscribeForNotifications(callback, keyPathsCollection);
30✔
103

104
    /// <summary>
105
    /// A convenience method that casts <see cref="IList{T}"/> to <see cref="IRealmCollection{T}"/> which implements
106
    /// <see cref="INotifyCollectionChanged"/>.
107
    /// </summary>
108
    /// <param name="list">The <see cref="IList{T}"/> to observe for changes.</param>
109
    /// <typeparam name="T">Type of the elements in the list.</typeparam>
110
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
111
    /// <returns>The collection, implementing <see cref="INotifyCollectionChanged"/>.</returns>
112
    public static IRealmCollection<T> AsRealmCollection<T>(this IList<T> list)
113
    {
114
        Argument.NotNull(list, nameof(list));
404✔
115

116
        return Argument.EnsureType<IRealmCollection<T>>(list, $"{nameof(list)} must be an instance of IRealmCollection<{typeof(T).Name}>.", nameof(list));
404✔
117
    }
118

119
    /// <summary>
120
    /// Converts a Realm-backed <see cref="IList{T}"/> to a Realm-backed <see cref="IQueryable{T}"/>.
121
    /// </summary>
122
    /// <typeparam name="T">The type of the objects contained in the list.</typeparam>
123
    /// <param name="list">The list of objects as obtained from a to-many relationship property.</param>
124
    /// <returns>A queryable collection that represents the objects contained in the list.</returns>
125
    /// <remarks>
126
    /// This method works differently from <see cref="Queryable.AsQueryable"/> in that it actually creates
127
    /// an underlying Realm query to represent the list. This means that all LINQ methods will be executed
128
    /// by the database and also that you can subscribe for notifications even after applying LINQ filters
129
    /// or ordering.
130
    /// </remarks>
131
    /// <example>
132
    /// <code>
133
    /// var dogs = owner.Dogs;
134
    /// var query = dogs.AsRealmQueryable()
135
    ///                 .Where(d => d.Age > 3)
136
    ///                 .OrderBy(d => d.Name);
137
    ///
138
    /// var token = query.SubscribeForNotifications((sender, changes, error) =>
139
    /// {
140
    ///     // You'll be notified only when dogs older than 3 have been added/removed/updated
141
    ///     // and the sender collection will be ordered by Name
142
    /// });
143
    /// </code>
144
    /// </example>
145
    /// <exception cref="ArgumentException">Thrown if the list is not managed by Realm.</exception>
146
    public static IQueryable<T> AsRealmQueryable<T>(this IList<T> list)
147
        where T : IRealmObjectBase
148
    {
149
        Argument.NotNull(list, nameof(list));
2✔
150

151
        var realmList = Argument.EnsureType<RealmList<T>>(list, $"{nameof(list)} must be a Realm List property.", nameof(list));
2✔
152
        return realmList.ToResults();
1✔
153
    }
154

155
    /// <summary>
156
    /// Converts a Realm-backed <see cref="ISet{T}"/> to a Realm-backed <see cref="IQueryable{T}"/>.
157
    /// </summary>
158
    /// <typeparam name="T">The type of the objects contained in the set.</typeparam>
159
    /// <param name="set">The set of objects as obtained from a to-many relationship property.</param>
160
    /// <returns>A queryable collection that represents the objects contained in the set.</returns>
161
    /// <remarks>
162
    /// This method works differently from <see cref="Queryable.AsQueryable"/> in that it actually creates
163
    /// an underlying Realm query to represent the set. This means that all LINQ methods will be executed
164
    /// by the database and also that you can subscribe for notifications even after applying LINQ filters
165
    /// or ordering.
166
    /// </remarks>
167
    /// <example>
168
    /// <code>
169
    /// var dogs = owner.Dogs;
170
    /// var query = dogs.AsRealmQueryable()
171
    ///                 .Where(d => d.Age > 3)
172
    ///                 .OrderBy(d => d.Name);
173
    ///
174
    /// var token = query.SubscribeForNotifications((sender, changes, error) =>
175
    /// {
176
    ///     // You'll be notified only when dogs older than 3 have been added/removed/updated
177
    ///     // and the sender collection will be ordered by Name
178
    /// });
179
    /// </code>
180
    /// </example>
181
    /// <exception cref="ArgumentException">Thrown if the list is not managed by Realm.</exception>
182
    public static IQueryable<T> AsRealmQueryable<T>(this ISet<T> set)
183
        where T : IRealmObjectBase
184
    {
185
        Argument.NotNull(set, nameof(set));
2✔
186

187
        var realmSet = Argument.EnsureType<RealmSet<T>>(set, $"{nameof(set)} must be a Realm Set property.", nameof(set));
2✔
188
        return realmSet.ToResults();
1✔
189
    }
190

191
    /// <summary>
192
    /// A convenience method that casts <see cref="IList{T}"/> to <see cref="IRealmCollection{T}"/> and subscribes for change notifications.
193
    /// </summary>
194
    /// <param name="list">The <see cref="IList{T}"/> to observe for changes.</param>
195
    /// <typeparam name="T">Type of the elements in the list.</typeparam>
196
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
197
    /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
198
    /// <param name="keyPathsCollection">An optional <see cref="KeyPathsCollection"/>, that indicates which changes in properties should raise a notification.</param>
199
    /// <returns>
200
    /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
201
    /// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
202
    /// </returns>
203
    public static IDisposable SubscribeForNotifications<T>(this IList<T> list, NotificationCallbackDelegate<T> callback,
204
        KeyPathsCollection? keyPathsCollection = null)
205
        => list.AsRealmCollection().SubscribeForNotifications(callback, keyPathsCollection);
115✔
206

207
    /// <summary>
208
    /// Move the specified item to a new position within the list.
209
    /// </summary>
210
    /// <param name="list">The list where the move should occur.</param>
211
    /// <param name="item">The item that will be moved.</param>
212
    /// <param name="index">The new position to which the item will be moved.</param>
213
    /// <typeparam name="T">Type of the objects in the list.</typeparam>
214
    /// <remarks>
215
    /// This extension method will work for standalone lists as well by calling <see cref="ICollection{T}.Remove"/>
216
    /// and then <see cref="IList{T}.Insert"/>.
217
    /// </remarks>
218
    /// <exception cref="ArgumentOutOfRangeException">Thrown if the index is less than 0 or greater than <see cref="ICollection{T}.Count"/> - 1.</exception>
219
    public static void Move<T>(this IList<T> list, T item, int index)
220
    {
221
        Argument.NotNull(list, nameof(list));
40✔
222

223
        var from = list.IndexOf(item);
40✔
224
        list.Move(from, index);
40✔
225
    }
32✔
226

227
    /// <summary>
228
    /// Move the specified item to a new position within the list.
229
    /// </summary>
230
    /// <param name="list">The list where the move should occur.</param>
231
    /// <param name="from">The index of the item that will be moved.</param>
232
    /// <param name="to">The new position to which the item will be moved.</param>
233
    /// <typeparam name="T">Type of the objects  in the list.</typeparam>
234
    /// <remarks>
235
    /// This extension method will work for standalone lists as well by calling <see cref="IList{T}.RemoveAt"/>
236
    /// and then <see cref="IList{T}.Insert"/>.
237
    /// </remarks>
238
    /// <exception cref="ArgumentOutOfRangeException">Thrown if the index is less than 0 or greater than <see cref="ICollection{T}.Count"/> - 1.</exception>
239
    public static void Move<T>(this IList<T> list, int from, int to)
240
    {
241
        Argument.NotNull(list, nameof(list));
1,168✔
242

243
        if (list is RealmList<T> realmList)
1,168✔
244
        {
245
            realmList.Move(from, to);
670✔
246
        }
247
        else
248
        {
249
            if (from < 0 || from >= list.Count)
498✔
250
            {
251
                throw new ArgumentOutOfRangeException(nameof(from));
194✔
252
            }
253

254
            if (to < 0 || to >= list.Count)
304✔
255
            {
256
                throw new ArgumentOutOfRangeException(nameof(to));
198✔
257
            }
258

259
            var item = list[from];
106✔
260
            list.RemoveAt(from);
106✔
261
            list.Insert(to, item);
106✔
262
        }
263
    }
106✔
264

265
    /// <summary>
266
    /// A convenience method that casts <see cref="IDictionary{String, T}"/> to <see cref="IRealmCollection{KeyValuePair}"/> which implements
267
    /// <see cref="INotifyCollectionChanged"/>.
268
    /// </summary>
269
    /// <param name="dictionary">The <see cref="IDictionary{String, T}"/> to observe for changes.</param>
270
    /// <typeparam name="T">Type of the elements in the dictionary.</typeparam>
271
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
272
    /// <returns>The collection, implementing <see cref="INotifyCollectionChanged"/>.</returns>
273
    public static IRealmCollection<KeyValuePair<string, T>> AsRealmCollection<T>(this IDictionary<string, T> dictionary)
274
    {
275
        Argument.NotNull(dictionary, nameof(dictionary));
1,021✔
276

277
        var realmDictionary = Argument.EnsureType<RealmDictionary<T>>(dictionary, $"{nameof(dictionary)} must be an instance of IRealmCollection<KeyValuePair<string, {typeof(T).Name}>>.", nameof(dictionary));
1,021✔
278
        return realmDictionary;
1,021✔
279
    }
280

281
    /// <summary>
282
    /// Converts a Realm-backed <see cref="IDictionary{String, T}"/> to a Realm-backed <see cref="IQueryable{T}"/> of dictionary's values.
283
    /// </summary>
284
    /// <typeparam name="T">The type of the values contained in the dictionary.</typeparam>
285
    /// <param name="dictionary">The dictionary of objects as obtained from a to-many relationship property.</param>
286
    /// <returns>A queryable collection that represents the values contained in the dictionary.</returns>
287
    /// <remarks>
288
    /// This method works differently from <see cref="Queryable.AsQueryable"/> in that it only returns a collection of values,
289
    /// not a collection of <see cref="KeyValuePair{String, T}"/> and it actually creates an underlying Realm query that represents the dictionary's values.
290
    /// This means that all LINQ methods will be executed by the database and also that you can subscribe for
291
    /// notifications even after applying LINQ filters or ordering.
292
    /// </remarks>
293
    /// <example>
294
    /// <code>
295
    /// var query = owner.DictOfDogs.AsRealmQueryable()
296
    ///                 .Where(d => d.Age > 3)
297
    ///                 .OrderBy(d => d.Name);
298
    ///
299
    /// var token = query.SubscribeForNotifications((sender, changes, error) =>
300
    /// {
301
    ///     // You'll be notified only when dogs older than 3 have been added/removed/updated
302
    ///     // and the sender collection will be ordered by Name
303
    /// });
304
    /// </code>
305
    /// </example>
306
    /// <exception cref="ArgumentException">Thrown if the dictionary is not managed by Realm.</exception>
307
    public static IQueryable<T> AsRealmQueryable<T>(this IDictionary<string, T?> dictionary)
308
        where T : IRealmObjectBase
309
    {
310
        Argument.NotNull(dictionary, nameof(dictionary));
2✔
311

312
        var realmDictionary = Argument.EnsureType<RealmDictionary<T>>(dictionary, $"{nameof(dictionary)} must be an instance of RealmDictionary<{typeof(T).Name}>.", nameof(dictionary));
2✔
313
        return realmDictionary.ToResults();
1✔
314
    }
315

316
    /// <summary>
317
    /// A convenience method that casts <see cref="IDictionary{String, T}"/> to <see cref="IRealmCollection{T}"/> and subscribes for change notifications.
318
    /// </summary>
319
    /// <param name="dictionary">The <see cref="IDictionary{String, T}"/> to observe for changes.</param>
320
    /// <typeparam name="T">Type of the elements in the dictionary.</typeparam>
321
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
322
    /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
323
    /// <param name="keyPathsCollection">An optional <see cref="KeyPathsCollection"/>, that indicates which changes in properties should raise a notification.</param>
324
    /// <returns>
325
    /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
326
    /// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
327
    /// </returns>
328
    public static IDisposable SubscribeForNotifications<T>(this IDictionary<string, T> dictionary,
329
        NotificationCallbackDelegate<KeyValuePair<string, T>> callback, KeyPathsCollection? keyPathsCollection = null)
330
    {
331
        return dictionary.AsRealmCollection().SubscribeForNotifications(callback, keyPathsCollection);
32✔
332
    }
333

334
    /// <summary>
335
    /// A convenience method that casts <see cref="IDictionary{String, T}"/> to <see cref="IRealmCollection{T}"/> and subscribes for key change notifications.
336
    /// </summary>
337
    /// <param name="dictionary">The <see cref="IDictionary{String, T}"/> to observe for changes.</param>
338
    /// <typeparam name="T">Type of the elements in the dictionary.</typeparam>
339
    /// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
340
    /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
341
    /// <returns>
342
    /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
343
    /// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
344
    /// </returns>
345
    public static IDisposable SubscribeForKeyNotifications<T>(this IDictionary<string, T> dictionary, DictionaryNotificationCallbackDelegate<T> callback)
346
    {
347
        Argument.NotNull(dictionary, nameof(dictionary));
33✔
348

349
        var realmDictionary = Argument.EnsureType<RealmDictionary<T>>(dictionary, $"{nameof(dictionary)} must be an instance of IRealmCollection<KeyValuePair<string, {typeof(T).Name}>>.", nameof(dictionary));
33✔
350
        return realmDictionary.SubscribeForKeyNotifications(callback);
33✔
351
    }
352

353
    /// <summary>
354
    /// Apply an NSPredicate-based filter over a collection. It can be used to create
355
    /// more complex queries, that are currently unsupported by the LINQ provider and
356
    /// supports SORT and DISTINCT clauses in addition to filtering.
357
    /// </summary>
358
    /// <typeparam name="T">The type of the objects that will be filtered.</typeparam>
359
    /// <param name="query">
360
    /// A Queryable collection, obtained by calling <see cref="Realm.All{T}"/>.
361
    /// </param>
362
    /// <param name="predicate">The predicate that will be applied.</param>
363
    /// <param name="arguments">
364
    /// Values used for substitution in the predicate.
365
    /// Note that all primitive types are accepted as they are implicitly converted to RealmValue.
366
    /// </param>
367
    /// <returns>A queryable observable collection of objects that match the predicate.</returns>
368
    /// <remarks>
369
    /// If you're not going to apply additional filters, it's recommended to use <see cref="AsRealmCollection{T}(IQueryable{T})"/>
370
    /// after applying the predicate.
371
    /// </remarks>
372
    /// <example>
373
    /// <code>
374
    /// var results1 = realm.All&lt;Foo&gt;("Bar.IntValue > 0");
375
    /// var results2 = realm.All&lt;Foo&gt;("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC)");
376
    /// var results3 = realm.All&lt;Foo&gt;("Bar.IntValue > 0 SORT(Bar.IntValue ASC Bar.StringValue DESC) DISTINCT(Bar.IntValue)");
377
    /// var results4 = realm.All&lt;Foo&gt;("Bar.IntValue > $0 || (Bar.String == $1 &amp;&amp; Bar.Bool == $2)", 5, "small", true);
378
    /// </code>
379
    /// </example>
380
    /// <seealso href="https://www.mongodb.com/docs/realm/realm-query-language/">
381
    /// Examples of the NSPredicate syntax
382
    /// </seealso>
383
    /// <seealso href="https://academy.realm.io/posts/nspredicate-cheatsheet/">NSPredicate Cheatsheet</seealso>
384
    public static IQueryable<T> Filter<T>(this IQueryable<T> query, string predicate, params QueryArgument[] arguments)
385
    {
386
        Argument.NotNull(predicate, nameof(predicate));
224✔
387
        Argument.NotNull(arguments, nameof(arguments));
224✔
388

389
        var realmResults = Argument.EnsureType<RealmResults<T>>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query));
223✔
390
        return realmResults.GetFilteredResults(predicate, arguments);
223✔
391
    }
392

393
    /// <summary>
394
    /// Apply an NSPredicate-based filter over a collection. It can be used to create
395
    /// more complex queries, that are currently unsupported by the LINQ provider and
396
    /// supports SORT and DISTINCT clauses in addition to filtering.
397
    /// </summary>
398
    /// <typeparam name="T">The type of the objects that will be filtered.</typeparam>
399
    /// <param name="list">A Realm List.</param>
400
    /// <param name="predicate">The predicate that will be applied.</param>
401
    /// <param name="arguments">
402
    /// Values used for substitution in the predicate.
403
    /// Note that all primitive types are accepted as they are implicitly converted to RealmValue.
404
    /// </param>
405
    /// <returns>A queryable observable collection of objects that match the predicate.</returns>
406
    /// <remarks>
407
    /// If you're not going to apply additional filters, it's recommended to use <see cref="AsRealmCollection{T}(IQueryable{T})"/>
408
    /// after applying the predicate.
409
    /// </remarks>
410
    /// <example>
411
    /// <code>
412
    /// var joe = realm.All&lt;Person&gt;().Single(p =&gt; p.Name == "Joe");
413
    /// joe.dogs.Filter("Name BEGINSWITH $0", "R");
414
    /// </code>
415
    /// </example>
416
    /// <seealso href="https://docs.mongodb.com/realm/reference/realm-query-language/"/>
417
    public static IQueryable<T> Filter<T>(this IList<T> list, string predicate, params QueryArgument[] arguments)
418
        where T : IRealmObjectBase
419
    {
420
        Argument.NotNull(predicate, nameof(predicate));
10✔
421
        Argument.NotNull(arguments, nameof(arguments));
10✔
422

423
        var realmList = Argument.EnsureType<RealmList<T>>(list, $"{nameof(list)} must be a Realm List property.", nameof(list));
10✔
424
        return realmList.GetFilteredResults(predicate, arguments);
9✔
425
    }
426

427
    /// <summary>
428
    /// Apply an NSPredicate-based filter over a collection. It can be used to create
429
    /// more complex queries, that are currently unsupported by the LINQ provider and
430
    /// supports SORT and DISTINCT clauses in addition to filtering.
431
    /// </summary>
432
    /// <typeparam name="T">The type of the objects that will be filtered.</typeparam>
433
    /// <param name="set">A Realm Set.</param>
434
    /// <param name="predicate">The predicate that will be applied.</param>
435
    /// <param name="arguments">
436
    /// Values used for substitution in the predicate.
437
    /// Note that all primitive types are accepted as they are implicitly converted to RealmValue.
438
    /// </param>
439
    /// <returns>A queryable observable collection of objects that match the predicate.</returns>
440
    /// <remarks>
441
    /// If you're not going to apply additional filters, it's recommended to use <see cref="AsRealmCollection{T}(IQueryable{T})"/>
442
    /// after applying the predicate.
443
    /// </remarks>
444
    /// <example>
445
    /// <code>
446
    /// var joe = realm.All&lt;Person&gt;().Single(p =&gt; p.Name == "Joe");
447
    /// joe.dogs.Filter("Name BEGINSWITH $0", "R");
448
    /// </code>
449
    /// </example>
450
    /// <seealso href="https://docs.mongodb.com/realm/reference/realm-query-language/">
451
    /// Examples of the NSPredicate syntax
452
    /// </seealso>
453
    /// <seealso href="https://academy.realm.io/posts/nspredicate-cheatsheet/">NSPredicate Cheatsheet</seealso>
454
    public static IQueryable<T> Filter<T>(this ISet<T> set, string predicate, params QueryArgument[] arguments)
455
        where T : IRealmObjectBase
456
    {
457
        Argument.NotNull(predicate, nameof(predicate));
10✔
458
        Argument.NotNull(arguments, nameof(arguments));
10✔
459

460
        var realmSet = Argument.EnsureType<RealmSet<T>>(set, $"{nameof(set)} must be a Realm Set property.", nameof(set));
10✔
461
        return realmSet.GetFilteredResults(predicate, arguments);
9✔
462
    }
463

464
    /// <summary>
465
    /// Apply an NSPredicate-based filter over dictionary's values. It can be used to create
466
    /// more complex queries, that are currently unsupported by the LINQ provider and
467
    /// supports SORT and DISTINCT clauses in addition to filtering.
468
    /// </summary>
469
    /// <typeparam name="T">The type of the dictionary's values that will be filtered.</typeparam>
470
    /// <param name="dictionary">A Realm Dictionary.</param>
471
    /// <param name="predicate">The predicate that will be applied.</param>
472
    /// <param name="arguments">
473
    /// Values used for substitution in the predicate.
474
    /// Note that all primitive types are accepted as they are implicitly converted to RealmValue.
475
    /// </param>
476
    /// <returns>A queryable observable collection of dictionary values that match the predicate.</returns>
477
    /// <remarks>
478
    /// If you're not going to apply additional filters, it's recommended to use <see cref="AsRealmCollection{T}(IQueryable{T})"/>
479
    /// after applying the predicate.
480
    /// </remarks>
481
    /// <example>
482
    /// <code>
483
    /// joe.DictOfDogs.Filter("Name BEGINSWITH $0", "R");
484
    /// </code>
485
    /// </example>
486
    /// <seealso href="https://docs.mongodb.com/realm/reference/realm-query-language/">
487
    /// Examples of the NSPredicate syntax
488
    /// </seealso>
489
    /// <seealso href="https://academy.realm.io/posts/nspredicate-cheatsheet/">NSPredicate Cheatsheet</seealso>
490
    public static IQueryable<T> Filter<T>(this IDictionary<string, T?> dictionary, string predicate, params QueryArgument[] arguments)
491
        where T : IRealmObjectBase
492
    {
493
        Argument.NotNull(predicate, nameof(predicate));
10✔
494
        Argument.NotNull(arguments, nameof(arguments));
10✔
495

496
        var realmDictionary = Argument.EnsureType<RealmDictionary<T>>(dictionary, $"{nameof(dictionary)} must be an instance of RealmDictionary<{typeof(T).Name}>.", nameof(dictionary));
10✔
497
        return realmDictionary.GetFilteredValueResults(predicate, arguments);
9✔
498
    }
499

500
    /// <summary>
501
    /// Adds a query to the set of active flexible sync subscriptions. The query will be joined via an OR statement
502
    /// with any existing queries for the same type.
503
    /// </summary>
504
    /// <param name="query">The query that will be matched on the server.</param>
505
    /// <param name="options">
506
    /// The subscription options controlling the name and/or the type of insert that will be performed.
507
    /// </param>
508
    /// <param name="waitForSync">
509
    /// A parameter controlling when this method should asynchronously wait for the server to send the objects
510
    /// matching the subscription.
511
    /// </param>
512
    /// <param name="cancellationToken">
513
    /// An optional cancellation token to cancel waiting for synchronization with the server. Note that cancelling the
514
    /// operation only cancels the wait itself and not the actual subscription, so the subscription will be added even
515
    /// if the task is cancelled. To remove the subscription, you can use <see cref="SubscriptionSet.Remove{T}"/>.
516
    /// </param>
517
    /// <typeparam name="T">The type of objects in the query results.</typeparam>
518
    /// <remarks>
519
    /// Adding a query that already exists is a no-op.
520
    /// <br/>
521
    /// This method is roughly equivalent to calling <see cref="SubscriptionSet.Add{T}"/> and then
522
    /// <see cref="SubscriptionSet.WaitForSynchronizationAsync"/>.
523
    /// </remarks>
524
    /// <returns>The original query after it has been added to the subscription set.</returns>
525
    /// <seealso cref="SubscriptionSet"/>
526
    public static async Task<IQueryable<T>> SubscribeAsync<T>(this IQueryable<T> query,
527
        SubscriptionOptions? options = null,
528
        WaitForSyncMode waitForSync = WaitForSyncMode.FirstTime,
529
        CancellationToken? cancellationToken = null)
530
        where T : IRealmObject
531
    {
532
        Argument.NotNull(query, nameof(query));
111✔
533

534
        var realmResults = Argument.EnsureType<RealmResults<T>>(query, $"{nameof(query)} must be a query obtained by calling Realm.All.", nameof(query));
111✔
535
        if (realmResults.Realm.Config is not FlexibleSyncConfiguration)
111!
536
        {
537
            throw new NotSupportedException(
×
538
                "SubscribeAsync can only be called on queries created in a flexible sync Realm (i.e. one open with a FlexibleSyncConfiguration)");
×
539
        }
540

541
        var subscriptions = realmResults.Realm.Subscriptions;
111✔
542
        var existingSub = options?.Name == null ? subscriptions.Find(query) : subscriptions.Find(options.Name);
111✔
543

544
        Subscription newSub = null!;
111✔
545

546
        subscriptions.Update(() =>
111✔
547
        {
111✔
548
            newSub = subscriptions.Add(realmResults, options);
111✔
549
        });
221✔
550

551
        if (ShouldWaitForSync(waitForSync, existingSub, newSub))
110✔
552
        {
553
            await subscriptions.WaitForSynchronizationAsync(cancellationToken);
105✔
554
            await realmResults.Realm.SyncSession.WaitForDownloadAsync(cancellationToken);
104✔
555
        }
556

557
        return query;
109✔
558
    }
109✔
559

560
    // TODO (ni): add docs
561
    public static T As<T>(this IDictionary<string, RealmValue> dict)
562
        where T : IMappedObject
563
    {
NEW
564
        var result = Activator.CreateInstance<T>();
×
NEW
565
        result.SetBackingStorage(dict);
×
NEW
566
        return result;
×
567
    }
568

569
    [EditorBrowsable(EditorBrowsableState.Never)]
570
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented",
571
        Justification = "This is only used by the weaver/source generated classes and should not be exposed to users.")]
572
    public static void PopulateCollection<T>(ICollection<T> source, ICollection<T> target, bool update, bool skipDefaults)
573
        => PopulateCollectionCore(source, target, update, skipDefaults, value => value);
91,553✔
574

575
    [EditorBrowsable(EditorBrowsableState.Never)]
576
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented",
577
        Justification = "This is only used by the weaver/source generated classes and should not be exposed to users.")]
578
    public static void PopulateCollection<T>(IDictionary<string, T> source, IDictionary<string, T> target, bool update, bool skipDefaults)
579
        => PopulateCollectionCore(source, target, update, skipDefaults, kvp => kvp.Value);
50,777✔
580

581
    private static bool ShouldWaitForSync(WaitForSyncMode mode, Subscription? oldSub, Subscription newSub)
582
    {
583
        switch (mode)
584
        {
585
            case WaitForSyncMode.Never:
586
                return false;
2!
587
            case WaitForSyncMode.FirstTime:
588
                // For FirstTimeSync mode we want to wait for sync only if we're adding a brand new sub
589
                // or if the sub changed object type/query.
590
                return oldSub == null ||
106✔
591
                       oldSub.ObjectType != newSub.ObjectType ||
106✔
592
                       oldSub.Query != newSub.Query;
106✔
593
            case WaitForSyncMode.Always:
594
                return true;
2✔
595
            default:
596
                throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
×
597
        }
598
    }
599

600
    private static void PopulateCollectionCore<T>(ICollection<T>? source, ICollection<T> target, bool update, bool skipDefaults, Func<T, object?> valueGetter)
601
    {
602
        Argument.NotNull(target, nameof(target));
139,472✔
603

604
        if (!skipDefaults || source != null)
139,472✔
605
        {
606
            target.Clear();
139,472✔
607
        }
608

609
        var realm = ((IRealmCollection<T>)target).Realm;
139,472✔
610

611
        if (source != null)
139,472✔
612
        {
613
            foreach (var item in source)
284,647✔
614
            {
615
                var value = valueGetter(item);
2,858✔
616
                if (value is IRealmObject obj)
2,858✔
617
                {
618
                    realm.Add(obj, update);
373✔
619
                }
620
                else if (value is RealmValue { Type: RealmValueType.Object } val)
2,485✔
621
                {
622
                    var wrappedObj = val.AsIRealmObject();
23✔
623
                    if (wrappedObj is IRealmObject robj)
23✔
624
                    {
625
                        realm.Add(robj, update);
17✔
626
                    }
627
                }
628

629
                target.Add(item);
2,857✔
630
            }
631
        }
632
    }
139,459✔
633
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc