Как получить автономный/неуправляемый объект RealmObject с использованием Realm Xamarin

Есть ли способ, когда я читаю объект из Realm, что он может стать автономным или неуправляемым объектом? В EF это называется отслеживанием. Использование для этого было бы, когда я хочу реализовать больше бизнес-логики для своих объектов данных до того, как они будут обновлены в постоянном хранилище данных. Я могу дать RealmObject для ViewModel, но когда изменения возвращаются из ViewModel, я хочу сравнить отключенный объект с объектом в хранилище данных, чтобы определить, что было изменено, поэтому, если бы был способ, которым я мог отключиться объект из Realm, когда я даю его ViewModel, тогда я могу лучше управлять тем, какие свойства изменились, используя мою логику логики, чтобы сделать то, что мне нужно, а затем сохранить изменения обратно в область.

Я понимаю, что в Realm много волшебства, и многие люди не захотят добавлять такой слой, как в этом приложении, но в моем приложении я не могу использовать UI, непосредственно обновляющий хранилище данных, если только не возникает событие, которое я могу подпишитесь, а затем присоедините мою бизнес-логику таким образом.

Я видел только одно событие и, похоже, не выполняет это действие.

Спасибо за вашу помощь.

Ответ 1

Пока он не добавлен в Realm для Xamarin, я добавил свойство моей модели, которое создает копию объекта. Кажется, это работает для моего использования. Сообщения об ошибках TwoWay Binding также не являются проблемой. Для более сложного приложения я не хочу размещать бизнес или логику данных в ViewModel. Это позволяет всем магам форм xamarin работать, и я реализую логику, когда наконец-то вернется время, чтобы сохранить изменения обратно в царство.

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

Однако, если есть какие-либо отношения, этот метод не работает для отношений. Копирование списка на самом деле не является вариантом, так как отношения can not существуют, если объект не привязан к Realm, я прочитал это где-то, но не могу найти его сейчас. Поэтому я предполагаю, что мы будем ждать дополнений к структуре.

Ответ 2

Сначала, json NUGET:

PM > Install-Package Newtonsoft.Json

И попробуйте этот "взломать" :

Дезертициализация измененного свойства IsManaged выполняет трюки.

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")
           );
}

.

Если вы столкнулись с замедлением на JsonConvert:

Согласно исходный код , свойство IsManaged имеет только get accessor и return true, когда доступно закрытое поле _realm

Итак, мы должны установить для экземпляра поля _realm значение null трюки

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;
}

.

Вы получите пустое тело RealmObject после того, как Realm теперь реализована с той же стратегией, что и LazyLoad

Запишите живой объект RealmObject и (деактивировать) экземпляр области в объекте Reflection. И установите записанные значения на RealmObject. С обработкой всех IList s внутри тоже.

        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;
        }

.

Для списка RealmObject, используя Select():

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

.

(Java) Вам не нужно это, если youre в java:

Возможно, когда-нибудь эта функция появится в .NET Realm

Realm.copyFromRealm();

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

Ответ 3

В настоящее время не существует интерфейса Xamarin, но мы можем его добавить. Интерфейс Java уже имеет copyFromRealm, который выполняет глубокую копию. Это также имеет парное слияние copyToRealmOrUpdate.

Подробнее см. Задача github в области для дальнейшего обсуждения.

Однако, как проблема дизайна, это действительно соответствует вашим потребностям оптимальным образом?

Я использовал конвертеры в приложениях WPF для вставки логики в привязку - это доступные в форматах Xamarin.

Другим способом в форматах Xamarin является использование Behaviors, представленное в блоге и охватывающее в API.

Эти подходы больше связаны с добавлением логики между UI и ViewModel, которые вы можете рассматривать как часть ViewModel, но до того, как обновления будут распространяться на связанные значения.

Ответ 4

Потратив слишком много времени на сторонние библиотеки, такие как AutoMapper, я создал собственную функцию расширения, которая работает довольно хорошо. Эта функция просто использует Reflection с рецессией. (В настоящее время только для типа "Список". Вы можете очень легко расширить функциональные возможности словаря и других типов коллекции или полностью изменить функциональность в соответствии со своими требованиями.).

Я не занимался анализом времени и сложности. Я тестировал только для моего тестового примера, который содержит много вложенных объектов RealmObject, построенных из строки 3500+ объекта JSON, для клонирования объекта потребовалось всего 15 миллисекунд.

Здесь полное расширение доступно через Github Gist. Если вы хотите расширить функциональность этого расширения, пожалуйста, обновите этот Github Gist, так что другие разработчики могут воспользоваться этим.

Здесь полное расширение -

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 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;
        }
    }
}