Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standalone objects support #221

Merged
merged 16 commits into from
Nov 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion RealmNet.Shared/Attributes.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/

using System;
using System.Reflection;

namespace RealmNet
{
Expand Down Expand Up @@ -30,4 +31,15 @@ public MapToAttribute(string mapping)
public class WovenAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Property)]
public class WovenPropertyAttribute : Attribute
{
internal string BackingFieldName { get; private set; }

public WovenPropertyAttribute(string backingFieldName)
{
this.BackingFieldName = backingFieldName;
}
}
}
28 changes: 27 additions & 1 deletion RealmNet.Shared/Realm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private static IntPtr GenerateObjectSchema(Type objectClass)
var objectType = "";
if (!p.PropertyType.IsValueType && p.PropertyType.Name!="String") {
if (p.PropertyType.Name == "RealmList`1")
objectType = p.PropertyType.GenericTypeArguments [0].Name;
objectType = p.PropertyType.GetGenericArguments()[0].Name;
else {
if (p.PropertyType.BaseType.Name == "RealmObject")
objectType = p.PropertyType.Name;
Expand Down Expand Up @@ -159,6 +159,32 @@ public object CreateObject(Type objectType)
return result;
}

public void Attach<T>(T obj) where T : RealmObject
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));

if (obj.IsManaged)
{
if (obj.Realm._sharedRealmHandle == this._sharedRealmHandle)
throw new RealmObjectAlreadyOwnedByRealmException("The object is already owned by this realm");

throw new RealmObjectOwnedByAnotherRealmException("Cannot attach an object to a realm when it's already owned by another realm");
}


if (!IsInTransaction)
throw new RealmOutsideTransactionException("Cannot attach a Realm object outside write transactions");

var tableHandle = _tableHandles[typeof(T)];

var rowPtr = NativeTable.add_empty_row(tableHandle);
var rowHandle = CreateRowHandle(rowPtr);

obj._Manage(this, rowHandle);
obj._CopyDataFromBackingFieldsToRow();
}

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
internal static RowHandle CreateRowHandle(IntPtr rowPtr)
{
Expand Down
12 changes: 12 additions & 0 deletions RealmNet.Shared/RealmList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public T this[int index]

public void Add(T item)
{
this.AttachObjectIfNeeded(item);
var rowIndex = ((RealmObject)item).RowHandle.RowIndex;
NativeLinkList.add(_listHandle, (IntPtr)rowIndex);
}
Expand Down Expand Up @@ -147,6 +148,9 @@ public IEnumerator<T> GetEnumerator()

public int IndexOf(T item)
{
if (!item.IsManaged)
throw new ArgumentException("Value does not belong to a realm", nameof(item));

var rowIndex = ((RealmObject)item).RowHandle.RowIndex;
return (int)NativeLinkList.find(_listHandle, (IntPtr)rowIndex, (IntPtr)0);
}
Expand All @@ -155,6 +159,8 @@ public void Insert(int index, T item)
{
if (index < 0)
throw new IndexOutOfRangeException ();

this.AttachObjectIfNeeded(item);
var rowIndex = ((RealmObject)item).RowHandle.RowIndex;
NativeLinkList.insert(_listHandle, (IntPtr)index, (IntPtr)rowIndex);
}
Expand All @@ -180,6 +186,12 @@ IEnumerator IEnumerable.GetEnumerator()
return new RealmListEnumerator(this);
}

private void AttachObjectIfNeeded(T obj)
{
if (!obj.IsManaged)
_parent.Realm.Attach(obj);
}

#endregion
}
}
2 changes: 2 additions & 0 deletions RealmNet.Shared/RealmNet.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Attributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmDecryptionFailedException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmObjectAlreadyOwnedByRealmException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmObjectOwnedByAnotherRealmException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmOutsideTransactionException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmFileExistsException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)exceptions\RealmInvalidDatabaseException.cs" />
Expand Down
46 changes: 39 additions & 7 deletions RealmNet.Shared/RealmObject.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

namespace RealmNet
Expand All @@ -12,14 +15,37 @@ public class RealmObject
private Realm _realm;
private RowHandle _rowHandle;

internal Realm Realm => _realm;
internal RowHandle RowHandle => _rowHandle;

internal protected bool IsManaged => _realm != null;

internal void _Manage(Realm realm, RowHandle rowHandle)
{
_realm = realm;
_rowHandle = rowHandle;
}

internal void _CopyDataFromBackingFieldsToRow()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is great for a first version. In the longer term, we want to have this copy be as fast as possible in case people are frequently creating objects standalone and assigning via Add. Something to think about is could we use Fody to generate an optimal copy method that wouldn't need to do all this reflection, creation of wovenProperties and iteration?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. In fact, @fealebenpae wanted to do this initally but we agreed that this solution is simpler and will do for the MVP.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great minds not only think alike but can arrive at same compromise (aka Engineering Decision ) :-D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kristiandupont and I had a conversation about this yesterday and I came up with the idea that we can weave a new method that does the copying so as to not incur the reflection costs. However we agreed on reflection for the time being until the performance considerations become important enough to necessitate more code weaving.

{
Debug.Assert(this.IsManaged);

var thisType = this.GetType();
var wovenProperties = from prop in thisType.GetProperties()
let backingField = prop.GetCustomAttributes(false)
.OfType<WovenPropertyAttribute>()
.Select(a => a.BackingFieldName)
.SingleOrDefault()
where backingField != null
select new { Info = prop, Field = thisType.GetField(backingField, BindingFlags.Instance | BindingFlags.NonPublic) };

foreach (var prop in wovenProperties)
{
var value = prop.Field.GetValue(this);
prop.Info.SetValue(this, value, null);
}
}

protected T GetValue<T>(string propertyName)
{
if (_realm == null)
Expand Down Expand Up @@ -131,20 +157,20 @@ protected void SetValue<T>(string propertyName, T value)
}


protected T GetListValue<T>(string propertyName) where T : RealmList<RealmObject>
protected RealmList<T> GetListValue<T>(string propertyName) where T : RealmObject
{
if (_realm == null)
throw new Exception("This object is not managed. Create through CreateObject");

var tableHandle = _realm._tableHandles[GetType()];
var columnIndex = NativeTable.get_column_index(tableHandle, propertyName, (IntPtr)propertyName.Length);
var listHandle = tableHandle.TableLinkList (columnIndex, _rowHandle);
var ret = (T)Activator.CreateInstance(typeof(T));
var ret = Activator.CreateInstance<RealmList<T>>();
ret.CompleteInit (this, listHandle);
return ret;
}

protected void SetListValue<T>(string propertyName, T value) where T : RealmList<RealmObject>
protected void SetListValue<T>(string propertyName, RealmList<T> value) where T : RealmObject
{
throw new NotImplementedException ("Setting a relationship list is not yet implemented");
}
Expand Down Expand Up @@ -187,10 +213,16 @@ protected void SetObjectValue<T>(string propertyName, T value) where T : RealmOb
var tableHandle = _realm._tableHandles[GetType()];
var columnIndex = NativeTable.get_column_index(tableHandle, propertyName, (IntPtr)propertyName.Length);
var rowIndex = _rowHandle.RowIndex;
if (value==null)
NativeTable.clear_link (tableHandle, columnIndex, (IntPtr)rowIndex);
if (value == null)
{
NativeTable.clear_link(tableHandle, columnIndex, (IntPtr)rowIndex);
}
else
NativeTable.set_link (tableHandle, columnIndex, (IntPtr)rowIndex, (IntPtr)value.RowHandle.RowIndex);
{
if (!value.IsManaged)
_realm.Attach(value);
NativeTable.set_link(tableHandle, columnIndex, (IntPtr)rowIndex, (IntPtr)value.RowHandle.RowIndex);
}

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/

namespace RealmNet
{
public class RealmObjectAlreadyOwnedByRealmException : RealmException
{
public RealmObjectAlreadyOwnedByRealmException(string detailMessage) : base(detailMessage)
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Copyright 2015 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/

namespace RealmNet
{
public class RealmObjectOwnedByAnotherRealmException : RealmException
{
public RealmObjectOwnedByAnotherRealmException(string detailMessage) : base(detailMessage)
{

}
}
}
Loading