LINQ support in Realm .NET
To make a query with Realm, you use the Realm.All<T>()
method to get an IQueryable<T>
instance. On this you can then apply the operators listed below.
Restriction Operators
Where
is supported. OfType
is not but it would be redundant as a query in Realm will always consist of a collection of the class initially specified.
Where
takes a predicate. To see the supported operations for predicates in Realm queries, refer to the Predicate Operations section.
Example:
var oldDogs = realm.All<Dog>().Where(dog => dog.Age > 8);
Ordering Operators
OrderBy
, OrderByDescending
, Thenby
, and ThenByDescending
are all supported. Reverse
is not yet supported. Currently, you can only order by persisted properties on the class that you are querying. This means that dogs.OrderBy(dog => dog.Owner.FirstName)
and the like is not yet supported.
Example:
var contacts = realm.All<Person>().OrderBy(p => p.LastName).ThenBy(p => p.FirstName);
Conversion Operators
ToArray
, ToList
, ToDictionary
, and ToLookup
are all supported. Cast
isn't, but it would be redundant as a query in Realm will always consist of a collection of the class initially specified.
Example:
var phoneBook = realm.All<Person>().ToDictionary(person => person.PhoneNumber);
Element Operators
All of the main element operators are supported:
First
andFirstOrDefault
Last
andLastOrDefault
Single
andSingleOrDefault
These methods take an optional predicate. To see the supported operations for predicates in Realm queries, refer to the Predicate Operations section.
Access to a single element by an index is supported by ElementAt
and ElementAtOrDefault
.
Note that, as is standard C# behaviour of default(T), the variants ...OrDefault
return a single null RealmObject
if there is no matching element.
DefaultIfEmpty
is not yet supported.
Quantifiers
Any
is supported.
All
and Contains
are not yet supported.
Any
takes an optional predicate. To see the supported operations for predicates in Realm queries, refer to the Predicate Operations section.
Aggregate Operators
Count
is supported.
LongCount
, Sum
, Min
, Max
, and Average
are not yet supported.
Count
takes an optional predicate. To see the supported operations for predicates in Realm queries, refer to the Predicate Operations section.
Predicate Operations
As a general rule, you can only create predicates with conditions that rely on data in Realm. Imagine a class
class Person : RealmObject
{
// Persisted properties
public string FirstName { get; set; }
public string LastName { get; set; }
// Non-persisted property
public string FullName => FirstName + " " + LastName;
}
Given this class, you can create queries with conditions that apply to the FirstName
and LastName
properties but not to the FullName
property. Likewise, properties with the [Ignored]
attribute cannot be used.
Note that currently, the property must be the left side of a condition. This means that
var oldDogs = realm.All<Dog>().Where(dog => 7 < dog.Age); // INVALID query, do not copy
is illegal and would have to be changed into the equivalent
var oldDogs = realm.All<Dog>().Where(dog => dog.Age > 7); // Fixed
Relational Operators
Equality operators can be applied to all property types: ==, !=
Furthermore, the following can be used for numerical types: <, <=, >, >=
String Operators
With strings, you can use:
Contains
, StartsWith
, and EndsWith
, Equals
, and Like
.
Example:
var peopleWhoseNameBeginsWithJ = realm.All<Person>.Where(p => p.FirstName.StartsWith("J"));
By default, Realm will perform a case-sensitive comparison, but you can provide StringComparison.OrdinalIgnoreCase
argument to overwrite that. Since there is no overload for Contains
that accepts StringComparison
on older .NET frameworks, we've provided a helper method that can be used when querying:
var peopleWhoseNameContainsA = realm.All<Person>().Where(p => QueryMethods.Contains(p.FirstName, "a", StringComparison.OrdinalIgnoreCase));
The Like
query method can be used to compare a string property against a pattern. ?
and *
are allowed as wildcard characters, where ?
matches 1 character and *
matches 0 or more characters:
var words = realm.All<Word>().Where(p => QueryMethods.Like(p.Value, "?bc*"));
// Matches abc, cbcde, but not bcd
When not used in a query expression, Like
falls back to using RegEx to enforce the same rules.
Composition
You can use parentheses and the || and && operators to compose queries.
Example:
var PuppyRexes = realm.All<Dog>().Where(dog => dog.Age < 2 && dog.Name == "Rex");
A note on liveness
Realm queries are live, in the sense that they will continue to represent the current state of the database.
realm.Write(() =>
{
realm.Add(new Person { FirstName = "John" });
realm.Add(new Person { FirstName = "Peter" });
});
var js = realm.All<Person>().Where(p => p.FirstName.StartsWith("J"));
foreach(var j in js)
{
Console.WriteLine(j.FirstName); // ==> John
}
realm.Write(() =>
{
realm.Add(new Person { FirstName = "Joe" });
});
foreach(var j in js)
{
Console.WriteLine(j.FirstName); // ==> John, Joe
}
This differs from the typical behavior of object/relational mappers (ORM's) where the result of a query is fetched and kept in memory as it was.
However, it also differs from the behavior of LINQ to Objects, where every iteration will reevaluate expressions, meaning that changes to both sides of a condition will affect the result. A Realm query will evaluate the right-hand sides of the conditions on the first run. So imagine you have a query like this:
var recentLogEntries = realm.All<LogEntry>().Where(l => l.TimeStamp > DateTime.Now.AddHours(-1));
Here, the recentLogEntries
variable will contain all the log entries that have a TimeStamp
later than one hour before the time when the query was first run (via foreach, ToList
etc.). Newly added log entries will be included on subsequent runs, but the time they are compared to will not be updated.
Not yet supported
The following features are not yet supported. A few of them will not be supported as the Realm architecture renders them unnecessary.
Grouping Operators
GroupBy
is not supported.
Set Operators
Distinct
, Union
, Intersect
, and Except
are not supported.
Partitioning Operators
Take
, Skip
, TakeWhile
, and SkipWhile
are not yet supported.
These are less important than when using an ORM. Given Realm's zero-copy pattern, data is only read from the database when the properties on the objects are accessed, so there is little overhead in simply iterating over a part of a result.
Projection Operators
Select
and SelectMany
are not yet supported.
The select keyword used with the query syntax is supported as long as you select the RealmObject
itself and not some derivative:
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;
Concatenation Operators
Concat
is not supported.
Join Operators
Join
and GroupJoin
are not supported.
Note that joins are less vital when using Realm than when using a relational database and an ORM. Instead of using keys to identify relations, you simply refer to the related object.
So given a class
public class Address : RealmObject
{
public string StreetName { get; set; }
public int Number { get; set; }
}
you can simply use that in another class like so:
public class Customer : RealmObject
{
public string Name { get; set; }
public Address Address { get; set; }
}
This works like an ordinary reference in C# which means that if two Customer
instances are assigned the same Address
object, changes to that address will apply to both customer objects.