Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a standalone / unmanaged RealmObject using Realm Xamarin

Tags:

xamarin

realm

Is there a way that when I read an object from Realm that it can become a standalone or unmanaged object? In EF, this is called no tracking. The usage for this would be when I want to implement more business logic on my data objects before they are updated on the persistent data storage. I may want to give the RealmObject to a ViewModel, but when the changes come back from the ViewModel, I want to compare the disconnected object to the object in the datastore to determine what was changed, so If there was a way that I could disconnect the object from Realm when I give it to the ViewModel, then I can better manage what properties have changed, using my biz logic to do what I need, then save the changes back to realm.

I understand Realm does a lot of magic and many people will not want to add a layer like this but in my app, I cant really have the UI directly updating the datastore, unless there is a event that is raised that I can subscribe too and then attach my business logic this way.

I only saw one event and it does not appear to perform this action.

Thanks for your assistance.

like image 439
J3RM Avatar asked Jun 23 '16 18:06

J3RM


3 Answers

Until its added to Realm for Xamarin, I added a property to my Model that creates a copy of the object. This seems to work for my use. The TwoWay Binding error messages are now also not an issue. For a more complicated application, I don't want to put business or data logic in the ViewModel. This allows all the Magic of xamarin forms to work and me to implement logic when its finally time to save the changes back to realm.

[Ignored]
    public Contact ToStandalone()
    {
        return new Contact()
        {
            companyName = this.companyName,
            dateAdded = this.dateAdded,
            fullName = this.fullName,
            gender = this.gender,
            website = this.website
        };
    }

However, If there are any relationships this method does not work for the relationships. Copying the List is not really an option either as the relationship cant exist if the object is not attached to Realm, I read this some where, can't find it to ref now. So I guess we will be waiting for the additions to the framework.

like image 143
J3RM Avatar answered Oct 02 '22 22:10

J3RM


First, get json NUGET :

PM> Install-Package Newtonsoft.Json

And, try this "hack" :

Deserialize the modified IsManaged property does the tricks.

public d DetachObject<d>(d Model) where d : RealmObject
{
    return Newtonsoft.Json.JsonConvert.DeserializeObject<d>(
               Newtonsoft.Json.JsonConvert.SerializeObject(Model)
               .Replace(",\"IsManaged\":true", ",\"IsManaged\":false")
           );
}

.

If you facing slow-down on JsonConvert:

According to source code , the 'IsManaged' property only has get accessor and return true when private field _realm which is available

So, we has to set the instance of field _realm to null does the tricks

public d DetachObject<d>(d Model) where d : RealmObject
{
    typeof(RealmObject).GetField("_realm", 
        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
        .SetValue(Model, null);
    return Model.IsManaged ? null : Model;
}

.

You will get empty RealmObject body after Realm are now implemented same strategy as LazyLoad

Record down live RealmObject and (deactivate) realm instance in object by Reflection. And set back recorded values to RealmObject. With handled all the ILists inside too.

        public d DetachObject<d>(d Model) where d : RealmObject
        {
            return (d)DetachObjectInternal(Model);
        }
        private object DetachObjectInternal(object Model)
        {
                //Record down properties and fields on RealmObject
            var Properties = Model.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
                .Where(x => x.Name != "ObjectSchema" && x.Name != "Realm" && x.Name != "IsValid" && x.Name != "IsManaged" && x.Name != "IsDefault")
                .Select(x =>(x.PropertyType.Name == "IList`1")? ("-" + x.Name, x.GetValue(Model)) : (x.Name, x.GetValue(Model))).ToList();
            var Fields = Model.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
                .Select(x => (x.Name, x.GetValue(Model))).ToList();
                //Unbind realm instance from object
            typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(Model, null);
                //Set back the properties and fields into RealmObject
            foreach (var field in Fields)
            {
                Model.GetType().GetField(field.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, field.Item2);
            }
            foreach (var property in Properties.OrderByDescending(x=>x.Item1[0]).ToList())
            {
                if (property.Item1[0] == '-')
                {
                    int count = (int)property.Item2.GetType().GetMethod("get_Count").Invoke(property.Item2, null);
                    if (count > 0)
                    {
                        if (property.Item2.GetType().GenericTypeArguments[0].BaseType.Name == "RealmObject")
                        {
                            for (int i = 0; i < count; i++)
                            {
                                var seter = property.Item2.GetType().GetMethod("set_Item");
                                var geter = property.Item2.GetType().GetMethod("get_Item");
                                property.Item2.GetType().GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public).SetValue(property.Item2, null);
                                DetachObjectInternal(geter.Invoke(property.Item2, new object[] { i }));
                            }
                        }
                    }
                }
                else
                {
                    Model.GetType().GetProperty(property.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, property.Item2);
                }
            }
            return Model;
        }

.

For List of the RealmObject , Using Select():

DBs.All<MyRealmObject>().ToList().Select(t => DBs.DetachObject(t)).ToList();

.

(Java)You dont need this if youre in java:

Maybe some day, this feature will come to .NET Realm

Realm.copyFromRealm();

#xamarin #C# #Realm #RealmObject #detach #managed #IsManaged #copyFromRealm

like image 21
nyconing Avatar answered Oct 02 '22 22:10

nyconing


Not currently in the Xamarin interface but we could add it. The Java interface already has copyFromRealm which performs a deep copy. That also has a paired merging copyToRealmOrUpdate.

See Realm github issue for further discussion.

However, as a design issue, is this really meeting your need in an optimal way?

I have used converters in WPF apps to insert logic into the binding - these are available in Xamarin Forms.

Another way in Xamarin forms is to use Behaviours, as introduced in the blog article and covered in the API.

These approaches are more about adding logic between the UI and ViewModel, which you could consider as part of the ViewModel, but before updates are propagated to bound values.

like image 31
Andy Dent Avatar answered Oct 02 '22 22:10

Andy Dent