【问题标题】:How to get a standalone / unmanaged RealmObject using Realm Xamarin如何使用 Realm Xamarin 获取独立/非托管 RealmObject
【发布时间】:2016-10-26 06:15:21
【问题描述】:

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

我知道 Realm 有很多魔力,很多人不想添加这样的层,但在我的应用程序中,我真的不能让 UI 直接更新数据存储,除非有一个我可以引发的事件也订阅,然后以这种方式附加我的业务逻辑。

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

感谢您的帮助。

【问题讨论】:

标签: xamarin realm


【解决方案1】:

目前不在 Xamarin 界面中,但我们可以添加它。 Java 接口已经有copyFromRealm,它执行深拷贝。这也有一个配对合并copyToRealmOrUpdate

更多讨论请见Realm github issue

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

我在 WPF 应用程序中使用 converters 将逻辑插入到绑定中 - 这些是 available in Xamarin Forms

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

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

【讨论】:

  • 在我的应用程序中,我需要知道 UI 更新的每个对象的每个属性,以便出于同步原因将其记录到表中。所以我真的需要一个带有逻辑的DataLayer。如果我正在创建一个只使用本地数据的应用程序,我会认为让所有对象“活动”或“管理”是有意义的。
【解决方案2】:

在将其添加到 Realm for 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
        };
    }

但是,如果存在任何关系,则此方法不适用于关系。复制列表也不是一个真正的选择,因为如果对象没有附加到领域,关系就不能存在,我在某个地方读到了这个,现在找不到它来引用。所以我想我们将等待添加到框架中。

【讨论】:

    【解决方案3】:

    首先,获取json NUGET

    PM> 安装包 Newtonsoft.Json

    然后,试试这个“hack”

    反序列化修改 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' 属性只有 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 的列表,使用 Select()

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

    .

    (Java)如果你在 java 中就不需要这个

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

    Realm.copyFromRealm();
    

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

    【讨论】:

    • 这看起来很有希望!您是否必须编译自己的 RealmObject 类副本,或者可以将其添加为扩展?
    • 我有 JsonConvert 方法工作,有明显的延迟。当我尝试使用完整方法时,我得到一个System.ArgumentException: Property set method not found.
    • 我的错 - 它试图处理只读属性。 :) 把这一切都解决了。感谢您的解决方法!
    【解决方案4】:

    在 AutoMapper 等 3rd 方库中浪费了太多时间后,我创建了自己的扩展函数,该函数运行良好。这个函数简单地使用反射与衰退。 (目前仅针对List类型。您可以非常轻松地扩展Dictionary和其他类型集合的功能,或者您可以根据自己的需求完全修改功能。)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多