Search code examples
xamarinrealm

Query Realm with multiple backlinks


The Realm documentation gives an example of backlinks using a person object and a dog object. If I extend this to include cats also, so a person can have several dogs or cats to walk, and each dog or cat can be walked by several different people.

public class Dog : RealmObject
{
    public string Name { get; set; }

    [Backlink(nameof(Person.Dogs))]
    public IQueryable<Person> Walkers { get; }
}

public class Cat : RealmObject
{
    public string Name { get; set; }

    [Backlink(nameof(Person.Cats))]
    public IQueryable<Person> Walkers { get; }
}

public class Person : RealmObject 
{
    //... other properties (name, age, address, etc.)
    public IList<Dog> Dogs { get; }
    public IList<Cat> Cats { get; }
}

Using the backlinks lets me get a list of people who walk the dog Fido...

var fidoWalkers = realm.All<Dog>().Where( d => d.Name == "Fido").FirstOrDefault().Walkers;

I can now further expand this query to find walkers of Fido who live in High Street or who are under 30 years old or whatever... great so far.

Now I want to get a list of people who walk the dog Fido and the cat Moggie. Using the backlinks in two separate statements I could get two result sets, one for Fido walkers and one for Moggie walkers, but I don't know how to combine them. Neither can I work out a query that would let me do this 'the long way round' without using the backlinks, because whenever I try to use

...Where( P => p.Dogs.Contains(Fido))...

I get 'System.NotSupportedException: The method 'Contains' is not supported'

Is there any way to get a list of people filtered by both the Dogs and the Cats lists?


Solution

  • While there are many things the .Net version of Realm does well, there are limitations in the Linq support and thus you are forced to materialize the lazy loaded IRealmCollection to a hard List (or array) via LINQ to Objects in order to perform projections, joins, IEqualityComparer comparisons, etc... this includes Backlinks and RealmObject-based IList properties.

    A must read Realm-dotnet document: LINQ support in Realm Xamarin

    You could add couple of properties that perform a Linq projection to a list of strings (pet names, of course assuming they are unique keys):

    public class Person : RealmObject
    {
        //... other properties (name, age, address, etc.)
        public IList<Dog> Dogs { get; }
        public IList<Cat> Cats { get; }
    
        public List<string> DogList => Dogs.ToList().Select(_ => _.Name).ToList();
        public List<string> CatList => Cats.ToList().Select(_ => _.Name).ToList();
    }
    

    And then take your person query to a list and use Contains on the materialized string lists vs. the RealmCollection properties.

    var walkers = realm.All<Person>().ToList().Where(_ => _.CatList.Contains("Garfield") && _.DogList.Contains("Fido"));
    foreach (var walker in walkers)
    {
        Log.Debug(TAG, $"{walker}");
    }
    

    You could also create two queries, one filtered on the dog's name backlink and cats for the other, materialized all the Dog and Cat collections on each Person in both queries, perform a Distinct via a custom IEqualityComparer.

    Once you have copied the objects out of Realm into independent lists/arrays, just about any Linq solution will work...

    The downside of this is all the filtering is being performed in memory thus you take a memory and CPU hit as you are not able to use Realm's native query features...

    Personally, I would re-model the RealmObject's to provide people that walk animals and thus animals are generic and they in turn provide links to particular Dog and Cat RealmObjects that provide specialization. But that is totally without knowing your end goal...