Is there a way that when I read an object from Realm that it can become a standalone or unmanaged object? In EF, this is called no tracking. The usage for this would be when I want to implement more business logic on my data objects before they are updated on the persistent data storage. I may want to give the RealmObject to a ViewModel, but when the changes come back from the ViewModel, I want to compare the disconnected object to the object in the datastore to determine what was changed, so If there was a way that I could disconnect the object from Realm when I give it to the ViewModel, then I can better manage what properties have changed, using my biz logic to do what I need, then save the changes back to realm.
I understand Realm does a lot of magic and many people will not want to add a layer like this but in my app, I cant really have the UI directly updating the datastore, unless there is a event that is raised that I can subscribe too and then attach my business logic this way.
I only saw one event and it does not appear to perform this action.
Thanks for your assistance.
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 IList
s 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;
}
}
}