How to get a standalone / unmanaged RealmObject using Realm Xamarin

风格不统一 提交于 2019-11-30 19:02:13

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

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.

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.

After wasting too much time in 3rd party libraries like AutoMapper, I've created my own extension function which works pretty well. This function simply uses Reflection with the recession. (Currently, Only for List type. You can extend the functionality for Dictionary and other types of the collection very easily or you can completely modify the functionality based on your own requirements.).

I didn't do too much time and complexity analysis. I've tested only for my test case which contains many nested RealmObject, built from a 3500+ line of JSON object, took only 15 milliseconds to clone the object.

Here the complete extension available via Github Gist. If you want to extend the functionality of this extension please update this Github Gist, So, other developers can take advantage of it.

Here the complete extension -

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Realms;

namespace ProjectName.Core.Extensions
{
    public static class RealmExtension
    {
        public static T Clone<T>(this T source) where T: new()
        {
            //If source is null return null
            if (source == null)
                return default(T);

            var target = new T();
            var targetType = typeof(T);

            //List of skip namespaces
            var skipNamespaces = new List<string>
            {
                typeof(Realm).Namespace
            };

            //Get the Namespace name of Generic Collection
            var collectionNamespace = typeof(List<string>).Namespace;

            //flags to get properties
            var flags = BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;

            //Get target properties list which follows the flags
            var targetProperties = targetType.GetProperties(flags);

            //if traget properties is null then return default target
            if (targetProperties == null)
                return target;

            //enumerate properties
            foreach (var property in targetProperties)
            {
                //skip property if it's belongs to namespace available in skipNamespaces list
                if (skipNamespaces.Contains(property.DeclaringType.Namespace))
                    continue;

                //Get property information and check if we can write value in it
                var propertyInfo = targetType.GetProperty(property.Name, flags);
                if (propertyInfo == null || !property.CanWrite)
                    continue;

                //Get value from the source
                var sourceValue = property.GetValue(source);

                //If property derived from the RealmObject then Clone that too
                if (property.PropertyType.IsSubclassOf(typeof(RealmObject)) && (sourceValue is RealmObject))
                {
                    var propertyType = property.PropertyType;
                    var convertedSourceValue = Convert.ChangeType(sourceValue, propertyType);
                    sourceValue = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
                            .MakeGenericMethod(propertyType).Invoke(convertedSourceValue, new[] { convertedSourceValue });
                }

                //Check if property belongs to the collection namespace and original value is not null
                if (property.PropertyType.Namespace == collectionNamespace && sourceValue != null)
                {
                    //get the type of the property (currently only supported List)
                    var listType = property.PropertyType;

                    //Create new instance of listType
                    var newList = (IList)Activator.CreateInstance(listType);

                    //Convert source value into the list type
                    var convertedSourceValue = Convert.ChangeType(sourceValue, listType) as IEnumerable;

                    //Enumerate source list and recursively call Clone method on each object
                    foreach (var item in convertedSourceValue)
                    {
                        var value = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
                            .MakeGenericMethod(item.GetType()).Invoke(item, new[] { item });
                        newList.Add(value);
                    }

                    //update source value
                    sourceValue = newList;
                }

                //set updated original value into the target
                propertyInfo.SetValue(target, sourceValue);
            }

            return target;
        }
    }
}


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!