如何使用Realm Xamarin获取独立/非托管的RealmObject

时间:2016-06-23 18:53:20

标签: xamarin realm

当我从Realm读取一个可以成为独立或非托管对象的对象时,有没有办法?在EF中,这称为无跟踪。这种情况的用法是,当我想要在持久数据存储上更新数据对象之前,在数据对象上实现更多业务逻辑。我可能想要将RealmObject提供给ViewModel,但是当从ViewModel返回更改时,我想将断开连接的对象与数据存储区中的对象进行比较以确定更改的内容,因此如果有一种方法可以断开连接当我将它提供给ViewModel时,Realm中的对象,然后我可以更好地管理已更改的属性,使用我的业务逻辑来执行我需要的操作,然后将更改保存回领域。

我理解Realm做了很多魔术,很多人都不想添加这样的图层但是在我的应用程序中,我真的不能直接更新数据存储区,除非有一个事件被提出我可以也订阅,然后以这种方式附加我的业务逻辑。

我只看到一个事件,但似乎没有执行此操作。

感谢您的协助。

4 个答案:

答案 0 :(得分:2)

直到它添加到Realm for Xamarin,我在我的Model中添加了一个属性,用于创建对象的副本。这似乎适合我的使用。 TwoWay绑定错误消息现在也不是问题。对于更复杂的应用程序,我不想将业务或数据逻辑放在ViewModel中。这允许所有魔术的xamarin表单工作,并且当它最终将更改保存回领域时,我实现逻辑。

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

但是,如果存在任何关系,则此方法不适用于关系。复制List实际上不是一个选项,因为如果对象没有附加到Realm,关系不能存在,我读了一些在哪里,现在找不到它。所以我想我们将等待框架的增加。

答案 1 :(得分:2)

首先,获取json NUGET

  

<强> PM&GT;安装包Newtonsoft.Json

并试试这个&#34; hack&#34;

反序列化已修改的 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上面临减速

根据source code ,'IsManaged&#39;当私有字段 get 可用时,该属性只有 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;
}

在Realm现在实现与 LazyLoad

相同的策略后,您将获得空RealmObject身体

通过 RealmObject 在对象中实时记录 Reflection 和(停用)领域实例。并将记录的值设置为 RealmObject同时处理了所有 IList

        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的列表,使用选择()

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

(Java)如果您使用的是java

,则不需要这样做

也许有一天,这个功能将来到.NET Realm

Realm.copyFromRealm();

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

答案 2 :(得分:1)

目前不在Xamarin界面,但我们可以添加它。 Java接口已经有copyFromRealm,它执行深层复制。这也有一个成对的合并copyToRealmOrUpdate

有关进一步的讨论,请参阅Realm github issue

但是,作为设计问题,这是否真的以最佳方式满足您的需求?

我在WPF应用中使用转换器将逻辑插入到绑定中 - 这些是available in Xamarin Forms

Xamarin表单中的另一种方法是使用行为,如blog article中介绍的那样,涵盖in the API

这些方法更多的是在UI和ViewModel之间添加逻辑,您可以将其视为ViewModel的一部分,但在更新传播到绑定值之前。

答案 3 :(得分:0)

在像AutoMapper这样的第三方库中浪费了很多时间之后,我创建了自己的扩展功能,效果很好。此功能仅将反射与衰退结合使用。 (当前,仅适用于列表类型。您可以非常轻松地扩展Dictionary和其他类型集合的功能,也可以根据自己的要求完全修改该功能。)

我没有做太多的时间和复杂性分析。我只测试了包含许多嵌套的RealmObject的测试用例,该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'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;
        }
    }
}