Getting exception when trying to insert one-to-many relationship


#1

I’m developing an application using XF, Prism and Realm. Currently, I have distributed my application within shared projects (Adderi.Abstractions, Adderi.Core, Adderi.Mobile.UI).

I register Realms instance on app initialization (containerRegistry.RegisterInstance(RealmModel.GetInstance()). Everything then works fine. The problem arises when I try to link a one-to-many relationship:

var productsList = new List<Product>();

var category = new Category
{
    CategoryId = "1",
    Description = "Test 1",
    SubDescription = "Test 1 - 1"
};

await CategoryRepository.Insert(category);

var productCategory = CategoryRepository.GetAll().Single(x => x.CategoryId == "1");

for (var i = 0; i <= 20; i++)
{
    var item = new Product
    {
        ProductId = i.ToString(),
        Description = $"Test {i.ToString()}",
        UnitPrice = 0,
        Unit = "ML",
        Category = productCategory
    };

    productsList.Add(item);
}

var result = await ProductRepository.Insert(productsList);

With this, I’m catching a RealmObjectManagedByAnotherRealmException. How can I solve this problem?


#2

Which line throws the error?


#3

HI @nirinchev! Sorry, there was a missing line (which I just added). I’ve edited my OP. This line throws the error: var result = await ProductRepository.Insert(productsList);

The Insert() method:

public async Task<bool> Insert(IList<Product> items)
{
    var result = await MakeChange(r => {
        foreach (var item in items)
        {
            r.Add(item);
        }
    });

    return result;
}

public async Task<bool> MakeChange(Action<Realm> action)
{
    var result = false;

    await realm.WriteAsync(action).ContinueWith(t =>
    {
        result = !t.IsFaulted && !t.IsCanceled && t.IsCompleted;
    });

    return result;
}

#4

The problem is that you’re trying to use an object from one thread in another. That is not supported by Realm - here’s the docs entry for it. Essentially, productCategory was obtained on the main thread and you’re trying to persist the product that references it on a background thread.

In general, Realm is a poor fit for a repository pattern because of the thread affinity and the “liveness” of Realm objects - it’s not impossible to make it work, but your code would be quite ugly and convoluted. A streamlined example of doing what you want to do without a repository would be:

var productCategory = ...;
var categoryReference = ThreadSafeReference.Create(productCategory);

await Task.Run(() =>
{
    var r = Realm.GetInstance(realm.Config);
    var backgroundCategory = r.ResolveReference(categoryReference);
    r.Write(() =>
    {
        for (var i = 0; i <= 20; i++)
        {
            r.Add(new Product
            {
                ProductId = i.ToString(),
                Description = $"Test {i.ToString()}",
                UnitPrice = 0,
                Unit = "ML",
                Category = backgroundCategory
            });
        });
    });
});

#5

Thanks @nirinchev! So, since I’m currently at an early stage, I guess my better bet would be to avoid using repositories, correct?

By the way, with the suggested approach, I’m getting a RealmInvalidTransactionException: “Cannot resolve thread safe reference during a write transaction”.


#6

Ugh :man_facepalming: I wrote that from memory without actually running it, sorry about that. I won’t be at a computer until later this evening, but I updated the snippet to something that will hopefully work.


#7

No problem, @nirinchev, I really appreciate your help. Thank you in advance!