From 3112d6c752b436b111ab4eb4ec3f839ab7065fd9 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Tue, 24 Nov 2015 15:38:41 +0100 Subject: [PATCH 01/11] Initial support for standalone objects --- RealmNet.Shared/RealmObject.cs | 2 + RealmNetWeaver/ModuleWeaver.cs | 79 ++++++++++++++++--- RealmNetWeaver/RealmNetWeaver.Fody.csproj | 3 + .../IntegrationTests.Shared.projitems | 1 + .../StandAloneObjectTests.cs | 35 ++++++++ 5 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 Tests/IntegrationTests.Shared/StandAloneObjectTests.cs diff --git a/RealmNet.Shared/RealmObject.cs b/RealmNet.Shared/RealmObject.cs index fbba3077cb..9865942c88 100644 --- a/RealmNet.Shared/RealmObject.cs +++ b/RealmNet.Shared/RealmObject.cs @@ -14,6 +14,8 @@ public class RealmObject internal RowHandle RowHandle => _rowHandle; + internal protected bool IsManaged => _realm != null; + internal void _Manage(Realm realm, RowHandle rowHandle) { _realm = realm; diff --git a/RealmNetWeaver/ModuleWeaver.cs b/RealmNetWeaver/ModuleWeaver.cs index 88f8c25bfb..3dab31f761 100644 --- a/RealmNetWeaver/ModuleWeaver.cs +++ b/RealmNetWeaver/ModuleWeaver.cs @@ -22,6 +22,8 @@ public class ModuleWeaver TypeSystem typeSystem; + MethodReference realmObjectIsManagedGetter; + // Init logging delegates to make testing easier public ModuleWeaver() { @@ -52,6 +54,7 @@ public void Execute() var assemblyToReference = ModuleDefinition.AssemblyResolver.Resolve("RealmNet"); var realmObjectType = assemblyToReference.MainModule.GetTypes().First(x => x.Name == "RealmObject"); + realmObjectIsManagedGetter = ModuleDefinition.ImportReference(realmObjectType.Properties.Single(x => x.Name == "IsManaged").GetMethod); var genericGetValueReference = MethodNamed(realmObjectType, "GetValue"); var genericSetValueReference = MethodNamed(realmObjectType, "SetValue"); //var getListValueReference = MethodNamed(realmObjectType, "GetListValue"); @@ -105,12 +108,36 @@ void AddGetter(PropertyDefinition prop, string columnName, MethodReference getVa var specializedGetValue = new GenericInstanceMethod(getValueReference); specializedGetValue.GenericArguments.Add(prop.PropertyType); - prop.GetMethod.Body.Instructions.Clear(); - var getProcessor = prop.GetMethod.Body.GetILProcessor(); - getProcessor.Emit(OpCodes.Ldarg_0); - getProcessor.Emit(OpCodes.Ldstr, columnName); - getProcessor.Emit(OpCodes.Call, specializedGetValue); - getProcessor.Emit(OpCodes.Ret); + /// A synthesized property getter looks like this: + /// 0: ldarg.0 + /// 1: ldfld + /// 2: ret + /// We want to change it so it looks like this: + /// 0: ldarg.0 + /// 1: call RealmNet.RealmObject.get_IsManaged + /// 2: brfalse.s 7 + /// 3: ldarg.0 + /// 4: ldstr + /// 5: call RealmNet.RealmObject.GetValue + /// 6: ret + /// 7: ldarg.0 + /// 8: ldfld + /// 9: ret + /// This is roughly equivalent to: + /// if (!base.IsManaged) return this.; + /// else return base.GetValue(); + + var start = prop.GetMethod.Body.Instructions.First(); + var il = prop.GetMethod.Body.GetILProcessor(); + + il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); + il.InsertBefore(start, il.Create(OpCodes.Call, realmObjectIsManagedGetter)); + il.InsertBefore(start, il.Create(OpCodes.Brfalse_S, start)); + il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); + il.InsertBefore(start, il.Create(OpCodes.Ldstr, columnName)); + il.InsertBefore(start, il.Create(OpCodes.Call, specializedGetValue)); + il.InsertBefore(start, il.Create(OpCodes.Ret)); + Debug.Write("[get] "); } @@ -120,13 +147,39 @@ void AddSetter(PropertyDefinition prop, string columnName, MethodReference setVa var specializedSetValue = new GenericInstanceMethod(setValueReference); specializedSetValue.GenericArguments.Add(prop.PropertyType); - prop.SetMethod.Body.Instructions.Clear(); - var setProcessor = prop.SetMethod.Body.GetILProcessor(); - setProcessor.Emit(OpCodes.Ldarg_0); - setProcessor.Emit(OpCodes.Ldstr, columnName); - setProcessor.Emit(OpCodes.Ldarg_1); - setProcessor.Emit(OpCodes.Call, specializedSetValue); - setProcessor.Emit(OpCodes.Ret); + /// A synthesized property setter looks like this: + /// 0: ldarg.0 + /// 1: ldarg.1 + /// 2: stfld + /// 3: ret + /// We want to change it so it looks like this: + /// 0: ldarg.0 + /// 1: call RealmNet.RealmObject.get_IsManaged + /// 2: brfalse.s 8 + /// 3: ldarg.0 + /// 4: ldstr + /// 5: ldarg.1 + /// 6: call RealmNet.RealmObject.SetValue + /// 7: ret + /// 8: ldarg.0 + /// 9: ldarg.1 + /// 10: stfld + /// 11: ret + /// This is roughly equivalent to: + /// if (!base.IsManaged) this. = value; + /// else base.SetValue(, value); + + var start = prop.SetMethod.Body.Instructions.First(); + var il = prop.SetMethod.Body.GetILProcessor(); + + il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); + il.InsertBefore(start, il.Create(OpCodes.Call, realmObjectIsManagedGetter)); + il.InsertBefore(start, il.Create(OpCodes.Brfalse_S, start)); + il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); + il.InsertBefore(start, il.Create(OpCodes.Ldstr, columnName)); + il.InsertBefore(start, il.Create(OpCodes.Ldarg_1)); + il.InsertBefore(start, il.Create(OpCodes.Call, specializedSetValue)); + il.InsertBefore(start, il.Create(OpCodes.Ret)); Debug.Write("[set] "); } diff --git a/RealmNetWeaver/RealmNetWeaver.Fody.csproj b/RealmNetWeaver/RealmNetWeaver.Fody.csproj index 4a6ce942df..9a150e17af 100755 --- a/RealmNetWeaver/RealmNetWeaver.Fody.csproj +++ b/RealmNetWeaver/RealmNetWeaver.Fody.csproj @@ -68,4 +68,7 @@ xcopy/Y $(TargetPath) $(SolutionDir)Tools cp $(TargetPath) $(SolutionDir)Tools + + xcopy /Y "$(TargetPath)" "$(SolutionDir)Tools" + \ No newline at end of file diff --git a/Tests/IntegrationTests.Shared/IntegrationTests.Shared.projitems b/Tests/IntegrationTests.Shared/IntegrationTests.Shared.projitems index 97808be1e5..7c71424d89 100644 --- a/Tests/IntegrationTests.Shared/IntegrationTests.Shared.projitems +++ b/Tests/IntegrationTests.Shared/IntegrationTests.Shared.projitems @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs new file mode 100644 index 0000000000..e7ebbc6e94 --- /dev/null +++ b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace IntegrationTests.Shared +{ + [TestFixture] + public class StandAloneObjectTests + { + private Person _person; + + [SetUp] + public void SetUp() + { + _person = new Person(); + } + + [Test] + public void PropertyGet() + { + string firstName = null; + Assert.DoesNotThrow(() => firstName = _person.FirstName); + Assert.IsNullOrEmpty(firstName); + } + + [Test] + public void PropertySet() + { + const string name = "John"; + Assert.DoesNotThrow(() => _person.FirstName = name); + Assert.AreEqual(name, _person.FirstName); + } + } +} From 8460deb5d951e852420a9122d69aaa9c34d21765 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 12:49:16 +0100 Subject: [PATCH 02/11] Standalone objects can be added to a realm --- RealmNet.Shared/Attributes.cs | 14 +++++++++- RealmNet.Shared/Realm.cs | 17 ++++++++++++ RealmNet.Shared/RealmObject.cs | 25 ++++++++++++++++- RealmNetWeaver/ModuleWeaver.cs | 21 ++++++++++++++- .../IntegrationTests.cs | 13 +++++++++ .../StandAloneObjectTests.cs | 27 +++++++++++++++++++ .../IntegrationTests.Win32.csproj | 4 +++ .../Win32StandaloneObjectTests.cs | 15 +++++++++++ 8 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 Tests/IntegrationTests.Win32/Win32StandaloneObjectTests.cs diff --git a/RealmNet.Shared/Attributes.cs b/RealmNet.Shared/Attributes.cs index 5a3f1c8ed7..74796261e4 100644 --- a/RealmNet.Shared/Attributes.cs +++ b/RealmNet.Shared/Attributes.cs @@ -1,8 +1,9 @@ /* Copyright 2015 Realm Inc - All Rights Reserved * Proprietary and Confidential */ - + using System; +using System.Reflection; namespace RealmNet { @@ -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; + } + } } diff --git a/RealmNet.Shared/Realm.cs b/RealmNet.Shared/Realm.cs index b6db418945..21a561ea6d 100644 --- a/RealmNet.Shared/Realm.cs +++ b/RealmNet.Shared/Realm.cs @@ -148,6 +148,23 @@ public object CreateObject(Type objectType) return result; } + public void Add(T obj) where T : RealmObject + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + + if (!IsInTransaction) + throw new RealmOutsideTransactionException("Cannot add 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)] private static RowHandle CreateRowHandle(IntPtr rowPtr) { diff --git a/RealmNet.Shared/RealmObject.cs b/RealmNet.Shared/RealmObject.cs index 9865942c88..7fc1675745 100644 --- a/RealmNet.Shared/RealmObject.cs +++ b/RealmNet.Shared/RealmObject.cs @@ -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 @@ -22,6 +25,26 @@ internal void _Manage(Realm realm, RowHandle rowHandle) _rowHandle = rowHandle; } + internal void _CopyDataFromBackingFieldsToRow() + { + Debug.Assert(this.IsManaged); + + var thisType = this.GetType(); + var wovenProperties = from prop in thisType.GetProperties() + let backingField = prop.GetCustomAttributes(false) + .OfType() + .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(string propertyName) { if (_realm == null) diff --git a/RealmNetWeaver/ModuleWeaver.cs b/RealmNetWeaver/ModuleWeaver.cs index 3dab31f761..02a93b23cc 100644 --- a/RealmNetWeaver/ModuleWeaver.cs +++ b/RealmNetWeaver/ModuleWeaver.cs @@ -63,6 +63,11 @@ public void Execute() var wovenAttributeClass = assemblyToReference.MainModule.GetTypes().First(x => x.Name == "WovenAttribute"); var wovenAttributeConstructor = ModuleDefinition.Import(wovenAttributeClass.GetConstructors().First()); + var wovenPropertyAttributeClass = assemblyToReference.MainModule.GetTypes().First(x => x.Name == "WovenPropertyAttribute"); + var wovenPropertyAttributeConstructor = ModuleDefinition.ImportReference(wovenPropertyAttributeClass.GetConstructors().First()); + var corlib = ModuleDefinition.AssemblyResolver.Resolve((AssemblyNameReference)ModuleDefinition.TypeSystem.CoreLibrary); + var stringType = ModuleDefinition.ImportReference(corlib.MainModule.GetType("System.String")); + foreach (var type in GetMatchingTypes()) { Debug.WriteLine("Weaving " + type.Name); @@ -73,6 +78,8 @@ public void Execute() if (mapToAttribute != null) columnName = ((string)mapToAttribute.ConstructorArguments[0].Value); + var backingField = GetBackingField(prop); + Debug.Write(" -- Property: " + prop.Name + " (column: " + columnName + ".. "); //TODO check if has either setter or getter and adjust accordingly - https://github.com/realm/realm-dotnet/issues/101 if (prop.PropertyType.Namespace == "RealmNet" && prop.PropertyType.Name == "RealmList`1") @@ -92,6 +99,10 @@ public void Execute() throw new NotSupportedException($"class '{type.Name}' field '{columnName}' is a {prop.PropertyType.Name} which is not yet supported"); } + var wovenPropertyAttribute = new CustomAttribute(wovenPropertyAttributeConstructor); + wovenPropertyAttribute.ConstructorArguments.Add(new CustomAttributeArgument(stringType, backingField.Name)); + prop.CustomAttributes.Add(wovenPropertyAttribute); + Debug.WriteLine(""); } @@ -102,7 +113,6 @@ public void Execute() return; } - void AddGetter(PropertyDefinition prop, string columnName, MethodReference getValueReference) { var specializedGetValue = new GenericInstanceMethod(getValueReference); @@ -204,4 +214,13 @@ void AddHelloWorld( TypeDefinition newType) processor.Emit(OpCodes.Ret); newType.Methods.Add(method); } + + private static FieldReference GetBackingField(PropertyDefinition property) + { + return property.GetMethod.Body.Instructions + .Where(o => o.OpCode == OpCodes.Ldfld) + .Select(o => o.Operand) + .OfType() + .SingleOrDefault(); + } } \ No newline at end of file diff --git a/Tests/IntegrationTests.Shared/IntegrationTests.cs b/Tests/IntegrationTests.Shared/IntegrationTests.cs index 199c9198f1..abef8a317f 100644 --- a/Tests/IntegrationTests.Shared/IntegrationTests.cs +++ b/Tests/IntegrationTests.Shared/IntegrationTests.cs @@ -194,6 +194,19 @@ public void CreateObjectOutsideTransactionShouldFail() Assert.Throws(() => _realm.CreateObject()); } + [Test] + public void AddOutsideTransactionShouldFail() + { + var obj = new Person(); + Assert.Throws(() => _realm.Add(obj)); + } + + [Test] + public void AddNullObjectShouldFail() + { + Assert.Throws(() => _realm.Add(null as Person)); + } + [Test] public void SetPropertyOutsideTransactionShouldFail() { diff --git a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs index e7ebbc6e94..6393cea1c0 100644 --- a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs +++ b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs @@ -1,6 +1,9 @@ using NUnit.Framework; +using RealmNet; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; namespace IntegrationTests.Shared @@ -31,5 +34,29 @@ public void PropertySet() Assert.DoesNotThrow(() => _person.FirstName = name); Assert.AreEqual(name, _person.FirstName); } + + [Test] + public void AddToRealm() + { + _person.FirstName = "Arthur"; + _person.LastName = "Dent"; + _person.IsInteresting = true; + + using (var realm = Realm.GetInstance(Path.GetTempFileName())) + { + using (var transaction = realm.BeginWrite()) + { + realm.Add(_person); + transaction.Commit(); + } + + Assert.That(_person.IsManaged); + + var p = realm.All().ToList().Single(); + Assert.That(p.FirstName, Is.EqualTo("Arthur")); + Assert.That(p.LastName, Is.EqualTo("Dent")); + Assert.That(p.IsInteresting); + } + } } } diff --git a/Tests/IntegrationTests.Win32/IntegrationTests.Win32.csproj b/Tests/IntegrationTests.Win32/IntegrationTests.Win32.csproj index 991bfc88d7..9d8649adaa 100644 --- a/Tests/IntegrationTests.Win32/IntegrationTests.Win32.csproj +++ b/Tests/IntegrationTests.Win32/IntegrationTests.Win32.csproj @@ -78,6 +78,7 @@ + @@ -95,6 +96,9 @@ + + + diff --git a/Tests/IntegrationTests.Win32/Win32StandaloneObjectTests.cs b/Tests/IntegrationTests.Win32/Win32StandaloneObjectTests.cs new file mode 100644 index 0000000000..46092e1b05 --- /dev/null +++ b/Tests/IntegrationTests.Win32/Win32StandaloneObjectTests.cs @@ -0,0 +1,15 @@ +using IntegrationTests.Shared; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntegrationTests.Win32 +{ + [TestFixture] + public class Win32StandAloneObjectTests : StandAloneObjectTests + { + } +} From b11075b9963494b63cc20e1bf2afe55e159e0dab Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 13:07:21 +0100 Subject: [PATCH 03/11] Adding objects already owned by another realm should fail --- RealmNet.Shared/Realm.cs | 10 ++++++++++ RealmNet.Shared/RealmNet.Shared.projitems | 1 + RealmNet.Shared/RealmObject.cs | 1 + .../RealmObjectOwnedByAnotherRealmException.cs | 14 ++++++++++++++ .../IntegrationTests.Shared/IntegrationTests.cs | 16 ++++++++++++++++ 5 files changed, 42 insertions(+) create mode 100644 RealmNet.Shared/exceptions/RealmObjectOwnedByAnotherRealmException.cs diff --git a/RealmNet.Shared/Realm.cs b/RealmNet.Shared/Realm.cs index 21a561ea6d..fa8d2ef7a9 100644 --- a/RealmNet.Shared/Realm.cs +++ b/RealmNet.Shared/Realm.cs @@ -153,6 +153,16 @@ public void Add(T obj) where T : RealmObject if (obj == null) throw new ArgumentNullException(nameof(obj)); + if (obj.IsManaged) + { + // the object is already owned by this realm, so do nothing I guess + if (obj.Realm._sharedRealmHandle == this._sharedRealmHandle) + return; + + throw new RealmObjectOwnedByAnotherRealmException("Cannot add an object to a realm when it's already owned by another realm"); + } + + if (!IsInTransaction) throw new RealmOutsideTransactionException("Cannot add a Realm object outside write transactions"); diff --git a/RealmNet.Shared/RealmNet.Shared.projitems b/RealmNet.Shared/RealmNet.Shared.projitems index afe0f3cf2b..bbe06e1cbe 100644 --- a/RealmNet.Shared/RealmNet.Shared.projitems +++ b/RealmNet.Shared/RealmNet.Shared.projitems @@ -11,6 +11,7 @@ + diff --git a/RealmNet.Shared/RealmObject.cs b/RealmNet.Shared/RealmObject.cs index 7fc1675745..001cc8413e 100644 --- a/RealmNet.Shared/RealmObject.cs +++ b/RealmNet.Shared/RealmObject.cs @@ -15,6 +15,7 @@ public class RealmObject private Realm _realm; private RowHandle _rowHandle; + internal Realm Realm => _realm; internal RowHandle RowHandle => _rowHandle; internal protected bool IsManaged => _realm != null; diff --git a/RealmNet.Shared/exceptions/RealmObjectOwnedByAnotherRealmException.cs b/RealmNet.Shared/exceptions/RealmObjectOwnedByAnotherRealmException.cs new file mode 100644 index 0000000000..76796ac328 --- /dev/null +++ b/RealmNet.Shared/exceptions/RealmObjectOwnedByAnotherRealmException.cs @@ -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) + { + + } + } +} diff --git a/Tests/IntegrationTests.Shared/IntegrationTests.cs b/Tests/IntegrationTests.Shared/IntegrationTests.cs index abef8a317f..ee05b9fc50 100644 --- a/Tests/IntegrationTests.Shared/IntegrationTests.cs +++ b/Tests/IntegrationTests.Shared/IntegrationTests.cs @@ -207,6 +207,22 @@ public void AddNullObjectShouldFail() Assert.Throws(() => _realm.Add(null as Person)); } + [Test] + public void AddAnObjectFromAnotherRealmShouldFail() + { + Person p; + using (var transaction = _realm.BeginWrite()) + { + p = _realm.CreateObject(); + transaction.Commit(); + } + + using (var otherRealm = Realm.GetInstance(Path.GetTempFileName())) + { + Assert.Throws(() => otherRealm.Add(p)); + } + } + [Test] public void SetPropertyOutsideTransactionShouldFail() { From 4c5d3cc39b0142133f3b3122c27ad90d47778a92 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 13:40:43 +0100 Subject: [PATCH 04/11] Fix RealmWeaver's PostBuild to be xplat again --- RealmNetWeaver/RealmNetWeaver.Fody.csproj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/RealmNetWeaver/RealmNetWeaver.Fody.csproj b/RealmNetWeaver/RealmNetWeaver.Fody.csproj index 9a150e17af..b7cb18850e 100755 --- a/RealmNetWeaver/RealmNetWeaver.Fody.csproj +++ b/RealmNetWeaver/RealmNetWeaver.Fody.csproj @@ -65,10 +65,7 @@ - xcopy/Y $(TargetPath) $(SolutionDir)Tools - cp $(TargetPath) $(SolutionDir)Tools - - - xcopy /Y "$(TargetPath)" "$(SolutionDir)Tools" + xcopy /Y "$(TargetPath)" "$(SolutionDir)Tools" + cp "$(TargetPath)" "$(SolutionDir)Tools" \ No newline at end of file From 2b8707d8a2c45294e531fdf4595283094aae1218 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 13:49:37 +0100 Subject: [PATCH 05/11] It shouldn't be possible to add an object to the same Realm more than once --- RealmNet.Shared/Realm.cs | 2 +- RealmNet.Shared/RealmNet.Shared.projitems | 1 + .../RealmObjectAlreadyOwnedByRealmException.cs | 14 ++++++++++++++ Tests/IntegrationTests.Shared/IntegrationTests.cs | 13 +++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 RealmNet.Shared/exceptions/RealmObjectAlreadyOwnedByRealmException.cs diff --git a/RealmNet.Shared/Realm.cs b/RealmNet.Shared/Realm.cs index fa8d2ef7a9..f939481b81 100644 --- a/RealmNet.Shared/Realm.cs +++ b/RealmNet.Shared/Realm.cs @@ -157,7 +157,7 @@ public void Add(T obj) where T : RealmObject { // the object is already owned by this realm, so do nothing I guess if (obj.Realm._sharedRealmHandle == this._sharedRealmHandle) - return; + throw new RealmObjectAlreadyOwnedByRealmException("The object is already owned by this realm"); throw new RealmObjectOwnedByAnotherRealmException("Cannot add an object to a realm when it's already owned by another realm"); } diff --git a/RealmNet.Shared/RealmNet.Shared.projitems b/RealmNet.Shared/RealmNet.Shared.projitems index bbe06e1cbe..ca28a40718 100644 --- a/RealmNet.Shared/RealmNet.Shared.projitems +++ b/RealmNet.Shared/RealmNet.Shared.projitems @@ -11,6 +11,7 @@ + diff --git a/RealmNet.Shared/exceptions/RealmObjectAlreadyOwnedByRealmException.cs b/RealmNet.Shared/exceptions/RealmObjectAlreadyOwnedByRealmException.cs new file mode 100644 index 0000000000..dce1af26de --- /dev/null +++ b/RealmNet.Shared/exceptions/RealmObjectAlreadyOwnedByRealmException.cs @@ -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) + { + + } + } +} diff --git a/Tests/IntegrationTests.Shared/IntegrationTests.cs b/Tests/IntegrationTests.Shared/IntegrationTests.cs index ee05b9fc50..14d0e9f8bd 100644 --- a/Tests/IntegrationTests.Shared/IntegrationTests.cs +++ b/Tests/IntegrationTests.Shared/IntegrationTests.cs @@ -223,6 +223,19 @@ public void AddAnObjectFromAnotherRealmShouldFail() } } + [Test] + public void AddAnObjectToRealmItAlreadyBelongsToShouldFail() + { + Person p; + using (var transaction = _realm.BeginWrite()) + { + p = _realm.CreateObject(); + transaction.Commit(); + } + + Assert.Throws(() => _realm.Add(p)); + } + [Test] public void SetPropertyOutsideTransactionShouldFail() { From 5e1a59871b8122e4812c1a6a7fac18b4f612e01c Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 15:29:22 +0100 Subject: [PATCH 06/11] Rename Add to Attach --- RealmNet.Shared/Realm.cs | 7 +++---- .../IntegrationTests.Shared/IntegrationTests.cs | 16 ++++++++-------- .../StandAloneObjectTests.cs | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/RealmNet.Shared/Realm.cs b/RealmNet.Shared/Realm.cs index 7af9b956f4..6050b54329 100644 --- a/RealmNet.Shared/Realm.cs +++ b/RealmNet.Shared/Realm.cs @@ -159,23 +159,22 @@ public object CreateObject(Type objectType) return result; } - public void Add(T obj) where T : RealmObject + public void Attach(T obj) where T : RealmObject { if (obj == null) throw new ArgumentNullException(nameof(obj)); if (obj.IsManaged) { - // the object is already owned by this realm, so do nothing I guess if (obj.Realm._sharedRealmHandle == this._sharedRealmHandle) throw new RealmObjectAlreadyOwnedByRealmException("The object is already owned by this realm"); - throw new RealmObjectOwnedByAnotherRealmException("Cannot add an object to a realm when it's already owned by another 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 add a Realm object outside write transactions"); + throw new RealmOutsideTransactionException("Cannot attach a Realm object outside write transactions"); var tableHandle = _tableHandles[typeof(T)]; diff --git a/Tests/IntegrationTests.Shared/IntegrationTests.cs b/Tests/IntegrationTests.Shared/IntegrationTests.cs index 14d0e9f8bd..f79ef40661 100644 --- a/Tests/IntegrationTests.Shared/IntegrationTests.cs +++ b/Tests/IntegrationTests.Shared/IntegrationTests.cs @@ -195,20 +195,20 @@ public void CreateObjectOutsideTransactionShouldFail() } [Test] - public void AddOutsideTransactionShouldFail() + public void AttachOutsideTransactionShouldFail() { var obj = new Person(); - Assert.Throws(() => _realm.Add(obj)); + Assert.Throws(() => _realm.Attach(obj)); } [Test] - public void AddNullObjectShouldFail() + public void AttachNullObjectShouldFail() { - Assert.Throws(() => _realm.Add(null as Person)); + Assert.Throws(() => _realm.Attach(null as Person)); } [Test] - public void AddAnObjectFromAnotherRealmShouldFail() + public void AttachAnObjectFromAnotherRealmShouldFail() { Person p; using (var transaction = _realm.BeginWrite()) @@ -219,12 +219,12 @@ public void AddAnObjectFromAnotherRealmShouldFail() using (var otherRealm = Realm.GetInstance(Path.GetTempFileName())) { - Assert.Throws(() => otherRealm.Add(p)); + Assert.Throws(() => otherRealm.Attach(p)); } } [Test] - public void AddAnObjectToRealmItAlreadyBelongsToShouldFail() + public void AttachAnObjectToRealmItAlreadyBelongsToShouldFail() { Person p; using (var transaction = _realm.BeginWrite()) @@ -233,7 +233,7 @@ public void AddAnObjectToRealmItAlreadyBelongsToShouldFail() transaction.Commit(); } - Assert.Throws(() => _realm.Add(p)); + Assert.Throws(() => _realm.Attach(p)); } [Test] diff --git a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs index 6393cea1c0..0a3d8e69f3 100644 --- a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs +++ b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs @@ -46,7 +46,7 @@ public void AddToRealm() { using (var transaction = realm.BeginWrite()) { - realm.Add(_person); + realm.Attach(_person); transaction.Commit(); } From df30bc26d64cf2dee5c6fd44ec32c13d4d79028d Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 25 Nov 2015 15:42:19 +0100 Subject: [PATCH 07/11] Replace the conditional PostBuildEvents that copy the Weaver with a Copy task --- RealmNetWeaver/RealmNetWeaver.Fody.csproj | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/RealmNetWeaver/RealmNetWeaver.Fody.csproj b/RealmNetWeaver/RealmNetWeaver.Fody.csproj index b7cb18850e..4413588f4e 100755 --- a/RealmNetWeaver/RealmNetWeaver.Fody.csproj +++ b/RealmNetWeaver/RealmNetWeaver.Fody.csproj @@ -64,8 +64,7 @@ - - xcopy /Y "$(TargetPath)" "$(SolutionDir)Tools" - cp "$(TargetPath)" "$(SolutionDir)Tools" - + + + \ No newline at end of file From 1b5db28584a80a4116d4c6290ccb844d6e1da3cb Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Thu, 26 Nov 2015 11:38:56 +0100 Subject: [PATCH 08/11] Implicitly attach standalone objects to a realm when adding to a relationship --- RealmNet.Shared/Realm.cs | 2 +- RealmNet.Shared/RealmList.cs | 12 +++++++ RealmNet.Shared/RealmObject.cs | 12 +++++-- .../RelationshipTests.cs | 33 +++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/RealmNet.Shared/Realm.cs b/RealmNet.Shared/Realm.cs index 6050b54329..9235226787 100644 --- a/RealmNet.Shared/Realm.cs +++ b/RealmNet.Shared/Realm.cs @@ -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; diff --git a/RealmNet.Shared/RealmList.cs b/RealmNet.Shared/RealmList.cs index ff8faad095..f65b86b4d2 100644 --- a/RealmNet.Shared/RealmList.cs +++ b/RealmNet.Shared/RealmList.cs @@ -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); } @@ -147,6 +148,9 @@ public IEnumerator 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); } @@ -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); } @@ -180,6 +186,12 @@ IEnumerator IEnumerable.GetEnumerator() return new RealmListEnumerator(this); } + private void AttachObjectIfNeeded(T obj) + { + if (!obj.IsManaged) + _parent.Realm.Attach(obj); + } + #endregion } } \ No newline at end of file diff --git a/RealmNet.Shared/RealmObject.cs b/RealmNet.Shared/RealmObject.cs index 1c0814b1f3..c59f52fe48 100644 --- a/RealmNet.Shared/RealmObject.cs +++ b/RealmNet.Shared/RealmObject.cs @@ -213,10 +213,16 @@ protected void SetObjectValue(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); + } } diff --git a/Tests/IntegrationTests.Shared/RelationshipTests.cs b/Tests/IntegrationTests.Shared/RelationshipTests.cs index a97ebd9983..cfa1ed2849 100644 --- a/Tests/IntegrationTests.Shared/RelationshipTests.cs +++ b/Tests/IntegrationTests.Shared/RelationshipTests.cs @@ -266,5 +266,38 @@ public void TestExceptionsFromTimsDogsOutOfRange() Assert.Throws( () => scratch = tim.Dogs[99] ); } + [Test] + public void TestSettingStandAloneObjectToRelationship() + { + var owner = realm.All().ToList().First(); + var dog = new Dog { Name = "Astro" }; + + using (var trans = realm.BeginWrite()) + { + owner.TopDog = dog; + trans.Commit(); + } + + var dogAgain = realm.All().Where(d => d.Name == "Astro").ToList().SingleOrDefault(); + Assert.That(dogAgain, Is.Not.Null); + Assert.That(dog.IsManaged); + } + + [Test] + public void TestAddingStandAloneObjectToToManyRelationship() + { + var owner = realm.All().ToList().First(); + var dog = new Dog { Name = "Astro" }; + + using (var trans = realm.BeginWrite()) + { + owner.Dogs.Add(dog); + trans.Commit(); + } + + var dogAgain = realm.All().Where(d => d.Name == "Astro").ToList().SingleOrDefault(); + Assert.That(dogAgain, Is.Not.Null); + Assert.That(dog.IsManaged); + } } } \ No newline at end of file From ad78b2aa4e182770671a101209173240c6e57422 Mon Sep 17 00:00:00 2001 From: Andy Dent Date: Thu, 26 Nov 2015 20:51:48 +0800 Subject: [PATCH 09/11] Fix test to run with the NUnitLite in our Integration Tests on IOS --- Tests/IntegrationTests.Shared/StandAloneObjectTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs index 6393cea1c0..6386cec984 100644 --- a/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs +++ b/Tests/IntegrationTests.Shared/StandAloneObjectTests.cs @@ -24,7 +24,7 @@ public void PropertyGet() { string firstName = null; Assert.DoesNotThrow(() => firstName = _person.FirstName); - Assert.IsNullOrEmpty(firstName); + Assert.That(string.IsNullOrEmpty(firstName)); } [Test] From 01f44dd90aa2b54867fe31511d37e7e5115707b2 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Thu, 26 Nov 2015 14:42:28 +0100 Subject: [PATCH 10/11] Make Lists build and run for me --- RealmNet.Shared/RealmObject.cs | 6 +++--- RealmNetWeaver/ModuleWeaver.cs | 28 +++++++++++----------------- wrappers/wrappers.vcxproj | 3 ++- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/RealmNet.Shared/RealmObject.cs b/RealmNet.Shared/RealmObject.cs index c59f52fe48..9624bfcf04 100644 --- a/RealmNet.Shared/RealmObject.cs +++ b/RealmNet.Shared/RealmObject.cs @@ -157,7 +157,7 @@ protected void SetValue(string propertyName, T value) } - protected T GetListValue(string propertyName) where T : RealmList + protected RealmList GetListValue(string propertyName) where T : RealmObject { if (_realm == null) throw new Exception("This object is not managed. Create through CreateObject"); @@ -165,12 +165,12 @@ protected T GetListValue(string propertyName) where T : RealmList>(); ret.CompleteInit (this, listHandle); return ret; } - protected void SetListValue(string propertyName, T value) where T : RealmList + protected void SetListValue(string propertyName, RealmList value) where T : RealmObject { throw new NotImplementedException ("Setting a relationship list is not yet implemented"); } diff --git a/RealmNetWeaver/ModuleWeaver.cs b/RealmNetWeaver/ModuleWeaver.cs index e45c20c1d5..3ba0873d93 100644 --- a/RealmNetWeaver/ModuleWeaver.cs +++ b/RealmNetWeaver/ModuleWeaver.cs @@ -101,8 +101,8 @@ public void Execute() if (prop.PropertyType.Namespace == "System" && (prop.PropertyType.IsPrimitive || prop.PropertyType.Name == "String" || prop.PropertyType.Name == "DateTimeOffset")) // most common tested first { - AddGetter(prop, columnName, genericGetValueReference); - AddSetter(prop, columnName, genericSetValueReference); + ReplaceGetter(prop, columnName, new GenericInstanceMethod(genericGetValueReference) { GenericArguments = { prop.PropertyType } }); + ReplaceSetter(prop, columnName, new GenericInstanceMethod(genericSetValueReference) { GenericArguments = { prop.PropertyType } }); } else if (prop.PropertyType.Namespace == "RealmNet" && prop.PropertyType.Name == "RealmList`1") { @@ -112,8 +112,9 @@ public void Execute() } // we may handle things differently here to handle init with a braced collection - AddGetter(prop, columnName, genericGetListValueReference); - AddSetter(prop, columnName, genericSetListValueReference); + var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); + ReplaceGetter(prop, columnName, new GenericInstanceMethod(genericGetListValueReference) { GenericArguments = { elementType } }); + ReplaceSetter(prop, columnName, new GenericInstanceMethod(genericSetListValueReference) { GenericArguments = { elementType } }); } else if (IsRealmObject(prop.PropertyType)) { @@ -122,8 +123,8 @@ public void Execute() LogWarningPoint($"{type.Name}.{columnName} is not an automatic property but its type is a RealmObject which normally indicates a relationship", sequencePoint); } - AddGetter(prop, columnName, genericGetObjectValueReference); - AddSetter(prop, columnName, genericSetObjectValueReference); // with casting in the RealmObject methods, should just work + ReplaceGetter(prop, columnName, new GenericInstanceMethod(genericGetObjectValueReference) { GenericArguments = { prop.PropertyType } }); + ReplaceSetter(prop, columnName, new GenericInstanceMethod(genericSetObjectValueReference) { GenericArguments = { prop.PropertyType } }); // with casting in the RealmObject methods, should just work } else { LogErrorPoint($"class '{type.Name}' field '{columnName}' is a {prop.PropertyType} which is not yet supported", sequencePoint); @@ -143,11 +144,8 @@ public void Execute() return; } - void AddGetter(PropertyDefinition prop, string columnName, MethodReference getValueReference) + void ReplaceGetter(PropertyDefinition prop, string columnName, MethodReference getValueReference) { - var specializedGetValue = new GenericInstanceMethod(getValueReference); - specializedGetValue.GenericArguments.Add(prop.PropertyType); - /// A synthesized property getter looks like this: /// 0: ldarg.0 /// 1: ldfld @@ -175,18 +173,14 @@ void AddGetter(PropertyDefinition prop, string columnName, MethodReference getVa il.InsertBefore(start, il.Create(OpCodes.Brfalse_S, start)); il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(start, il.Create(OpCodes.Ldstr, columnName)); - il.InsertBefore(start, il.Create(OpCodes.Call, specializedGetValue)); + il.InsertBefore(start, il.Create(OpCodes.Call, getValueReference)); il.InsertBefore(start, il.Create(OpCodes.Ret)); Debug.Write("[get] "); } - - void AddSetter(PropertyDefinition prop, string columnName, MethodReference setValueReference) + void ReplaceSetter(PropertyDefinition prop, string columnName, MethodReference setValueReference) { - var specializedSetValue = new GenericInstanceMethod(setValueReference); - specializedSetValue.GenericArguments.Add(prop.PropertyType); - /// A synthesized property setter looks like this: /// 0: ldarg.0 /// 1: ldarg.1 @@ -218,7 +212,7 @@ void AddSetter(PropertyDefinition prop, string columnName, MethodReference setVa il.InsertBefore(start, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(start, il.Create(OpCodes.Ldstr, columnName)); il.InsertBefore(start, il.Create(OpCodes.Ldarg_1)); - il.InsertBefore(start, il.Create(OpCodes.Call, specializedSetValue)); + il.InsertBefore(start, il.Create(OpCodes.Call, setValueReference)); il.InsertBefore(start, il.Create(OpCodes.Ret)); Debug.Write("[set] "); diff --git a/wrappers/wrappers.vcxproj b/wrappers/wrappers.vcxproj index d12941d990..e53cac6484 100644 --- a/wrappers/wrappers.vcxproj +++ b/wrappers/wrappers.vcxproj @@ -96,7 +96,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;%(PreprocessorDefinitions) ..\..\realm-core\src;..\..\realm-core\src\win32\pthread;object-store\;object-store\impl;object-store\impl\windows;%(AdditionalIncludeDirectories) - /DPTW32_STATIC_LIB /DREALM_DEBUG /DREALM_HAVE_CONFIG=1 /DREALM_ENABLE_REPLICATION %(AdditionalOptions) + /DPTW32_STATIC_LIB /DREALM_DEBUG /DREALM_ENABLE_REPLICATION %(AdditionalOptions) Windows @@ -177,6 +177,7 @@ + From c41105112a032a94e8105a7a1e61f5d07ad78400 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Thu, 26 Nov 2015 16:17:00 +0100 Subject: [PATCH 11/11] Remove all REALM_HAVE_CONFIG definitions --- wrappers/wrappers.vcxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/wrappers.vcxproj b/wrappers/wrappers.vcxproj index e53cac6484..7fc6989e20 100644 --- a/wrappers/wrappers.vcxproj +++ b/wrappers/wrappers.vcxproj @@ -112,7 +112,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;%(PreprocessorDefinitions) ..\..\realm-core\src;..\..\realm-core\src\win32\pthread;object-store\;object-store\impl;object-store\impl\windows;%(AdditionalIncludeDirectories) - /DPTW32_STATIC_LIB /DREALM_DEBUG /DREALM_HAVE_CONFIG=1 /DREALM_ENABLE_REPLICATION %(AdditionalOptions) + /DPTW32_STATIC_LIB /DREALM_DEBUG /DREALM_ENABLE_REPLICATION %(AdditionalOptions) Windows @@ -128,7 +128,7 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;REALM_HAVE_CONFIG=1;REALM_ENABLE_REPLICATION;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;REALM_ENABLE_REPLICATION;%(PreprocessorDefinitions) ..\..\realm-core\src;..\..\realm-core\src\win32\pthread;object-store\;object-store\impl;object-store\impl\windows;%(AdditionalIncludeDirectories) @@ -147,7 +147,7 @@ MaxSpeed true true - WIN32;NDEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;REALM_HAVE_CONFIG=1;REALM_ENABLE_REPLICATION;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;WRAPPERS_EXPORTS;HAVE_STRUCT_TIMESPEC;REALM_ENABLE_REPLICATION;%(PreprocessorDefinitions) ..\..\realm-core\src;..\..\realm-core\src\win32\pthread;object-store\;object-store\impl;object-store\impl\windows;%(AdditionalIncludeDirectories)