Как получить автономный/неуправляемый объект 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;
}
}
}
Ответ 5
Исходя из ответа, я создал пакет NuGet для этого https://www.nuget.org/packages/Realm.Clone/
Исходный код: https://github.com/pfedotovsky/realm-dotnet-clone/blob/master/Realm.Clone/Realm.Clone/RealmExtensions.cs