diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcbc9613d8..8f6c064ffb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,10 @@
## vNext (TBD)
### Enhancements
-* None
+* Added support for `Migration.FindInNewRealm` which is a helper that allows you to lookup the object in the post-migration Realm that corresponds to an object from the pre-migration Realm. (Issue [#3600](https://github.com/realm/realm-dotnet/issues/3600))
### Fixed
-* None
+* Fixed an issue that would cause `RealmObject.DynamicApi.GetList/Set/Dictionary` to fail when the collection contains primitive values. (Issue [#3597](https://github.com/realm/realm-dotnet/issues/3597))
### Compatibility
* Realm Studio: 15.0.0 or later.
diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs
index add76a9f13..ff223c3e46 100644
--- a/Realm/Realm/Handles/SharedRealmHandle.cs
+++ b/Realm/Realm/Handles/SharedRealmHandle.cs
@@ -229,6 +229,9 @@ public static extern void rename_property(SharedRealmHandle sharedRealm,
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_operating_system", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr get_operating_system(IntPtr buffer, IntPtr buffer_length);
+ [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_get_object_for_object", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_object_for_object(SharedRealmHandle realmHandle, ObjectHandle handle, out NativeException ex);
+
#pragma warning restore SA1121 // Use built-in type alias
#pragma warning restore IDE0049 // Use built-in type alias
}
@@ -677,6 +680,21 @@ public bool TryFindObject(TableKey tableKey, in RealmValue id, [MaybeNullWhen(fa
return true;
}
+ public bool TryFindObject(ObjectHandle handle, [MaybeNullWhen(false)] out ObjectHandle objectHandle)
+ {
+ var result = NativeMethods.get_object_for_object(this, handle, out var ex);
+ ex.ThrowIfNecessary();
+
+ if (result == IntPtr.Zero)
+ {
+ objectHandle = null;
+ return false;
+ }
+
+ objectHandle = new ObjectHandle(this, result);
+ return true;
+ }
+
public void RenameProperty(string typeName, string oldName, string newName, IntPtr migrationSchema)
{
NativeMethods.rename_property(this, typeName, (IntPtr)typeName.Length,
diff --git a/Realm/Realm/Migration.cs b/Realm/Realm/Migration.cs
index 02dea33a7c..998e8bc738 100644
--- a/Realm/Realm/Migration.cs
+++ b/Realm/Realm/Migration.cs
@@ -115,5 +115,27 @@ public void RenameProperty(string typeName, string oldPropertyName, string newPr
NewRealm.SharedRealmHandle.RenameProperty(typeName, oldPropertyName, newPropertyName, _migrationSchema);
}
+
+ ///
+ /// Finds an object obtained from in .
+ ///
+ /// The type of the object in the new realm.
+ /// The object obtained from the old realm.
+ /// The corresponding object post-migration or null if the object no longer exists in the new realm.
+ ///
+ ///
+ /// foreach (var oldPerson in migration.OldRealm.DynamicApi.All("Person"))
+ /// {
+ /// var newPerson = migration.FindInNewRealm<Person>(oldPerson)
+ /// newPerson.Name = $"{oldPerson.DynamicApi.Get<string>("FirstName")} {oldPerson.DynamicApi.Get<string>("LastName")}";
+ /// }
+ ///
+ ///
+ public T? FindInNewRealm(IRealmObject obj)
+ where T : IRealmObject
+ {
+ Argument.Ensure(obj.IsManaged, "Only managed RealmObject instances can be looked up in the new Realm", nameof(obj));
+ return NewRealm.FindExisting(obj);
+ }
}
}
diff --git a/Realm/Realm/Native/PrimitiveValue.cs b/Realm/Realm/Native/PrimitiveValue.cs
index 0f333fcfe9..b31ec8921b 100644
--- a/Realm/Realm/Native/PrimitiveValue.cs
+++ b/Realm/Realm/Native/PrimitiveValue.cs
@@ -353,7 +353,12 @@ public static StringValue AllocateFrom(string? value, Arena arena)
public static implicit operator bool(in StringValue value) => value.data != null;
- public static implicit operator string?(in StringValue value) => !value ? null : Encoding.UTF8.GetString(value.data, (int)value.size);
+ public static implicit operator string?(in StringValue value) => value.ToDotnetString();
+
+ public readonly string? ToDotnetString(bool treatEmptyAsNull = false)
+ => data == null || (size == 0 && treatEmptyAsNull)
+ ? null
+ : Encoding.UTF8.GetString(data, (int)size);
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs
index 45e2138ed8..9d741994d7 100644
--- a/Realm/Realm/Realm.cs
+++ b/Realm/Realm/Realm.cs
@@ -1111,6 +1111,21 @@ internal IQueryable AllEmbedded()
return default;
}
+ // This is only used during migrations. obj here is the object in the old realm, and we're trying to find
+ // its counterpart post-migration.
+ internal T? FindExisting(IRealmObject obj)
+ {
+ ThrowIfDisposed();
+ Argument.Ensure(obj.IsManaged, "Only managed objects can be used in FindExisting", nameof(obj));
+ if (SharedRealmHandle.TryFindObject(obj.GetObjectHandle()!, out var objectHandle))
+ {
+ var metadata = Metadata[typeof(T).GetMappedOrOriginalName()];
+ return (T)MakeObject(metadata, objectHandle);
+ }
+
+ return default;
+ }
+
#endregion Quick Find using primary key
#region Thread Handover
@@ -1589,7 +1604,7 @@ internal Dynamic(Realm realm)
public IRealmObjectBase CreateObject(string className) => CreateObjectCore(className, primaryKey: null);
///
- /// Factory for a managed object without a primary key in a realm. Only valid within a write .
+ /// Factory for a managed object with a primary key in a realm. Only valid within a write .
///
/// A dynamically-accessed Realm object.
/// The type of object to create as defined in the schema.
@@ -1609,7 +1624,7 @@ internal Dynamic(Realm realm)
public IRealmObjectBase CreateObject(string className, long? primaryKey) => CreateObjectCore(className, primaryKey);
///
- /// Factory for a managed object without a primary key in a realm. Only valid within a write .
+ /// Factory for a managed object with a primary key in a realm. Only valid within a write .
///
/// A dynamically-accessed Realm object.
/// The type of object to create as defined in the schema.
@@ -1629,7 +1644,7 @@ internal Dynamic(Realm realm)
public IRealmObjectBase CreateObject(string className, string? primaryKey) => CreateObjectCore(className, primaryKey);
///
- /// Factory for a managed object without a primary key in a realm. Only valid within a write .
+ /// Factory for a managed object with a primary key in a realm. Only valid within a write .
///
/// A dynamically-accessed Realm object.
/// The type of object to create as defined in the schema.
@@ -1649,7 +1664,7 @@ internal Dynamic(Realm realm)
public IRealmObjectBase CreateObject(string className, ObjectId? primaryKey) => CreateObjectCore(className, primaryKey);
///
- /// Factory for a managed object without a primary key in a realm. Only valid within a write .
+ /// Factory for a managed object with a primary key in a realm. Only valid within a write .
///
/// A dynamically-accessed Realm object.
/// The type of object to create as defined in the schema.
diff --git a/Realm/Realm/Schema/Property.cs b/Realm/Realm/Schema/Property.cs
index b2324b4ca4..2378756816 100644
--- a/Realm/Realm/Schema/Property.cs
+++ b/Realm/Realm/Schema/Property.cs
@@ -170,11 +170,10 @@ public Property(string name, PropertyType type, string? objectType = null, strin
internal Property(in SchemaProperty nativeProperty)
{
Name = nativeProperty.name!;
- string? managedName = nativeProperty.managed_name;
- ManagedName = !string.IsNullOrEmpty(managedName) ? managedName! : Name;
+ ManagedName = nativeProperty.managed_name.ToDotnetString(treatEmptyAsNull: true) ?? Name;
Type = nativeProperty.type;
- ObjectType = nativeProperty.object_type;
- LinkOriginPropertyName = nativeProperty.link_origin_property_name;
+ ObjectType = nativeProperty.object_type.ToDotnetString(treatEmptyAsNull: true);
+ LinkOriginPropertyName = nativeProperty.link_origin_property_name.ToDotnetString(treatEmptyAsNull: true);
IsPrimaryKey = nativeProperty.is_primary;
IndexType = nativeProperty.index;
}
diff --git a/Tests/Realm.Tests/Database/MigrationTests.cs b/Tests/Realm.Tests/Database/MigrationTests.cs
index 20ab122c9d..d507100660 100644
--- a/Tests/Realm.Tests/Database/MigrationTests.cs
+++ b/Tests/Realm.Tests/Database/MigrationTests.cs
@@ -834,6 +834,113 @@ public void Migration_ToEmbedded_DeletesOrphans()
Assert.That(newRealm.AllEmbedded().Count(), Is.EqualTo(1));
Assert.That(newRealm.AllEmbedded().Any(e => e.Value == "bar"), Is.False);
}
+
+ [Test]
+ public void Migration_FindInNewRealm_WhenObjectIsDeleted_ReturnsNull()
+ {
+ var oldConfig = new RealmConfiguration(Guid.NewGuid().ToString())
+ {
+ Schema = new[]
+ {
+ typeof(Dotnet_3597_Old)
+ }
+ };
+
+ using (var oldRealm = GetRealm(oldConfig))
+ {
+ oldRealm.Write(() =>
+ {
+ oldRealm.Add(new Dotnet_3597_Old { FloatProp = 1, IntProp = 1 });
+ oldRealm.Add(new Dotnet_3597_Old { FloatProp = 2, IntProp = 2 });
+ });
+ }
+
+ var newConfig = new RealmConfiguration(oldConfig.DatabasePath)
+ {
+ Schema = new[]
+ {
+ typeof(Dotnet_3597)
+ },
+ SchemaVersion = 2,
+ MigrationCallback = (migration, version) =>
+ {
+ var oldObjects = migration.OldRealm.DynamicApi.All(nameof(Dotnet_3597)).ToArray();
+ var old1 = oldObjects.First(o => o.DynamicApi.Get("IntProp") == 1);
+ var old2 = oldObjects.First(o => o.DynamicApi.Get("IntProp") == 2);
+
+ var newObjects = migration.NewRealm.All().ToArray();
+ var new1 = newObjects.First(o => o.IntProp == 1);
+ var new2 = newObjects.First(o => o.IntProp == 2);
+
+ Assert.That(migration.FindInNewRealm(old1), Is.EqualTo(new1));
+ Assert.That(migration.FindInNewRealm(old2), Is.EqualTo(new2));
+
+ migration.NewRealm.Remove(new1);
+
+ Assert.That(migration.FindInNewRealm(old1), Is.Null);
+ }
+ };
+
+ var newRealm = GetRealm(newConfig);
+ Assert.That(newRealm.All().Count(), Is.EqualTo(1));
+ }
+
+ // Test for https://github.com/realm/realm-dotnet/issues/3597
+ [Test]
+ public void Migration_MigratesListOfFloats()
+ {
+ var oldConfig = new RealmConfiguration(Guid.NewGuid().ToString())
+ {
+ Schema = new[]
+ {
+ typeof(Dotnet_3597_Old)
+ }
+ };
+
+ using (var oldRealm = GetRealm(oldConfig))
+ {
+ oldRealm.Write(() =>
+ {
+ oldRealm.Add(new Dotnet_3597_Old
+ {
+ FloatProp = 3.14f,
+ FloatList =
+ {
+ 1,
+ 2,
+ -1.23f
+ }
+ });
+ });
+ }
+
+ var newConfig = new RealmConfiguration(oldConfig.DatabasePath)
+ {
+ Schema = new[]
+ {
+ typeof(Dotnet_3597)
+ },
+ SchemaVersion = 2,
+ MigrationCallback = (migration, version) =>
+ {
+ foreach (var item in migration.OldRealm.DynamicApi.All(nameof(Dotnet_3597)))
+ {
+ var newItem = migration.FindInNewRealm(item)!;
+ newItem.FloatProp = item.DynamicApi.Get("FloatProp").ToString()!;
+ foreach (var floatValue in item.DynamicApi.GetList("FloatList"))
+ {
+ newItem.FloatList.Add(floatValue.ToString()!);
+ }
+ }
+ }
+ };
+
+ var newRealm = GetRealm(newConfig);
+
+ var obj = newRealm.All().Single();
+ Assert.That(obj.FloatProp, Is.EqualTo("3.14"));
+ CollectionAssert.AreEqual(obj.FloatList, new[] { "1", "2", "-1.23" });
+ }
}
[Explicit]
@@ -884,4 +991,26 @@ public partial class ObjectEmbedded : TestEmbeddedObject
{
public string? Value { get; set; }
}
+
+ [Explicit]
+ [MapTo("Dotnet_3597")]
+ public partial class Dotnet_3597_Old : TestRealmObject
+ {
+ public int IntProp { get; set; }
+
+ public float? FloatProp { get; set; }
+
+ public IList FloatList { get; }
+ }
+
+ [Explicit]
+ [MapTo("Dotnet_3597")]
+ public partial class Dotnet_3597 : TestRealmObject
+ {
+ public int IntProp { get; set; }
+
+ public string FloatProp { get; set; }
+
+ public IList FloatList { get; }
+ }
}
diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_Old_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_Old_generated.cs
new file mode 100644
index 0000000000..be32bf8ac4
--- /dev/null
+++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_Old_generated.cs
@@ -0,0 +1,449 @@
+//
+#nullable enable
+
+using MongoDB.Bson.Serialization;
+using NUnit.Framework;
+using Realms;
+using Realms.Exceptions;
+using Realms.Extensions;
+using Realms.Schema;
+using Realms.Tests.Database;
+using Realms.Weaving;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+using TestEmbeddedObject = Realms.IEmbeddedObject;
+using TestRealmObject = Realms.IRealmObject;
+
+namespace Realms.Tests.Database
+{
+ [Generated]
+ [Woven(typeof(Dotnet_3597_OldObjectHelper)), Realms.Preserve(AllMembers = true)]
+ public partial class Dotnet_3597_Old : IRealmObject, INotifyPropertyChanged, IReflectableType
+ {
+
+ [Realms.Preserve]
+ static Dotnet_3597_Old()
+ {
+ Realms.Serialization.RealmObjectSerializer.Register(new Dotnet_3597_OldSerializer());
+ }
+
+ ///
+ /// Defines the schema for the class.
+ ///
+ public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("Dotnet_3597", ObjectSchema.ObjectType.RealmObject)
+ {
+ Realms.Schema.Property.Primitive("IntProp", Realms.RealmValueType.Int, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "IntProp"),
+ Realms.Schema.Property.Primitive("FloatProp", Realms.RealmValueType.Float, isPrimaryKey: false, indexType: IndexType.None, isNullable: true, managedName: "FloatProp"),
+ Realms.Schema.Property.PrimitiveList("FloatList", Realms.RealmValueType.Float, areElementsNullable: true, managedName: "FloatList"),
+ }.Build();
+
+ #region IRealmObject implementation
+
+ private IDotnet_3597_OldAccessor? _accessor;
+
+ Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor;
+
+ private IDotnet_3597_OldAccessor Accessor => _accessor ??= new Dotnet_3597_OldUnmanagedAccessor(typeof(Dotnet_3597_Old));
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsManaged => Accessor.IsManaged;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsValid => Accessor.IsValid;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsFrozen => Accessor.IsFrozen;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.Realm? Realm => Accessor.Realm;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public int BacklinksCount => Accessor.BacklinksCount;
+
+ void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults)
+ {
+ var newAccessor = (IDotnet_3597_OldAccessor)managedAccessor;
+ var oldAccessor = _accessor;
+ _accessor = newAccessor;
+
+ if (helper != null && oldAccessor != null)
+ {
+ if (!skipDefaults)
+ {
+ newAccessor.FloatList.Clear();
+ }
+
+ if (!skipDefaults || oldAccessor.IntProp != default(int))
+ {
+ newAccessor.IntProp = oldAccessor.IntProp;
+ }
+ if (!skipDefaults || oldAccessor.FloatProp != default(float?))
+ {
+ newAccessor.FloatProp = oldAccessor.FloatProp;
+ }
+ Realms.CollectionExtensions.PopulateCollection(oldAccessor.FloatList, newAccessor.FloatList, update, skipDefaults);
+ }
+
+ if (_propertyChanged != null)
+ {
+ SubscribeForNotifications();
+ }
+
+ OnManaged();
+ }
+
+ #endregion
+
+ ///
+ /// Called when the object has been managed by a Realm.
+ ///
+ ///
+ /// This method will be called either when a managed object is materialized or when an unmanaged object has been
+ /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked,
+ /// it is not yet clear whether the object is managed or not.
+ ///
+ partial void OnManaged();
+
+ private event PropertyChangedEventHandler? _propertyChanged;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged
+ {
+ add
+ {
+ if (_propertyChanged == null)
+ {
+ SubscribeForNotifications();
+ }
+
+ _propertyChanged += value;
+ }
+
+ remove
+ {
+ _propertyChanged -= value;
+
+ if (_propertyChanged == null)
+ {
+ UnsubscribeFromNotifications();
+ }
+ }
+ }
+
+ ///
+ /// Called when a property has changed on this class.
+ ///
+ /// The name of the property.
+ ///
+ /// For this method to be called, you need to have first subscribed to .
+ /// This can be used to react to changes to the current object, e.g. raising for computed properties.
+ ///
+ ///
+ ///
+ /// class MyClass : IRealmObject
+ /// {
+ /// public int StatusCodeRaw { get; set; }
+ /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw;
+ /// partial void OnPropertyChanged(string propertyName)
+ /// {
+ /// if (propertyName == nameof(StatusCodeRaw))
+ /// {
+ /// RaisePropertyChanged(nameof(StatusCode));
+ /// }
+ /// }
+ /// }
+ ///
+ /// Here, we have a computed property that depends on a persisted one. In order to notify any
+ /// subscribers that StatusCode has changed, we implement and
+ /// raise manually by calling .
+ ///
+ partial void OnPropertyChanged(string? propertyName);
+
+ private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ OnPropertyChanged(propertyName);
+ }
+
+ private void SubscribeForNotifications()
+ {
+ Accessor.SubscribeForNotifications(RaisePropertyChanged);
+ }
+
+ private void UnsubscribeFromNotifications()
+ {
+ Accessor.UnsubscribeFromNotifications();
+ }
+
+ ///
+ /// Converts a to . Equivalent to .
+ ///
+ /// The to convert.
+ /// The stored in the .
+ public static explicit operator Dotnet_3597_Old?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject();
+
+ ///
+ /// Implicitly constructs a from .
+ ///
+ /// The value to store in the .
+ /// A containing the supplied .
+ public static implicit operator Realms.RealmValue(Dotnet_3597_Old? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val);
+
+ ///
+ /// Implicitly constructs a from .
+ ///
+ /// The value to store in the .
+ /// A containing the supplied .
+ public static implicit operator Realms.QueryArgument(Dotnet_3597_Old? val) => (Realms.RealmValue)val;
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this);
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (obj is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj is InvalidObject)
+ {
+ return !IsValid;
+ }
+
+ if (!(obj is Realms.IRealmObjectBase iro))
+ {
+ return false;
+ }
+
+ return Accessor.Equals(iro.Accessor);
+ }
+
+ ///
+ public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode();
+
+ ///
+ public override string? ToString() => Accessor.ToString();
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597_OldObjectHelper : Realms.Weaving.IRealmObjectHelper
+ {
+ public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults)
+ {
+ throw new InvalidOperationException("This method should not be called for source generated classes.");
+ }
+
+ public Realms.ManagedAccessor CreateAccessor() => new Dotnet_3597_OldManagedAccessor();
+
+ public Realms.IRealmObjectBase CreateInstance() => new Dotnet_3597_Old();
+
+ public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value)
+ {
+ value = RealmValue.Null;
+ return false;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ internal interface IDotnet_3597_OldAccessor : Realms.IRealmAccessor
+ {
+ int IntProp { get; set; }
+
+ float? FloatProp { get; set; }
+
+ System.Collections.Generic.IList FloatList { get; }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597_OldManagedAccessor : Realms.ManagedAccessor, IDotnet_3597_OldAccessor
+ {
+ public int IntProp
+ {
+ get => (int)GetValue("IntProp");
+ set => SetValue("IntProp", value);
+ }
+
+ public float? FloatProp
+ {
+ get => (float?)GetValue("FloatProp");
+ set => SetValue("FloatProp", value);
+ }
+
+ private System.Collections.Generic.IList _floatList = null!;
+ public System.Collections.Generic.IList FloatList
+ {
+ get
+ {
+ if (_floatList == null)
+ {
+ _floatList = GetListValue("FloatList");
+ }
+
+ return _floatList;
+ }
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597_OldUnmanagedAccessor : Realms.UnmanagedAccessor, IDotnet_3597_OldAccessor
+ {
+ public override ObjectSchema ObjectSchema => Dotnet_3597_Old.RealmSchema;
+
+ private int _intProp;
+ public int IntProp
+ {
+ get => _intProp;
+ set
+ {
+ _intProp = value;
+ RaisePropertyChanged("IntProp");
+ }
+ }
+
+ private float? _floatProp;
+ public float? FloatProp
+ {
+ get => _floatProp;
+ set
+ {
+ _floatProp = value;
+ RaisePropertyChanged("FloatProp");
+ }
+ }
+
+ public System.Collections.Generic.IList FloatList { get; } = new List();
+
+ public Dotnet_3597_OldUnmanagedAccessor(Type objectType) : base(objectType)
+ {
+ }
+
+ public override Realms.RealmValue GetValue(string propertyName)
+ {
+ return propertyName switch
+ {
+ "IntProp" => _intProp,
+ "FloatProp" => _floatProp,
+ _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"),
+ };
+ }
+
+ public override void SetValue(string propertyName, Realms.RealmValue val)
+ {
+ switch (propertyName)
+ {
+ case "IntProp":
+ IntProp = (int)val;
+ return;
+ case "FloatProp":
+ FloatProp = (float?)val;
+ return;
+ default:
+ throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}");
+ }
+ }
+
+ public override void SetValueUnique(string propertyName, Realms.RealmValue val)
+ {
+ throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique");
+ }
+
+ public override IList GetListValue(string propertyName)
+ {
+ return propertyName switch
+ {
+ "FloatList" => (IList)FloatList,
+ _ => throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"),
+ };
+ }
+
+ public override ISet GetSetValue(string propertyName)
+ {
+ throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}");
+ }
+
+ public override IDictionary GetDictionaryValue(string propertyName)
+ {
+ throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}");
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597_OldSerializer : Realms.Serialization.RealmObjectSerializerBase
+ {
+ public override string SchemaName => "Dotnet_3597";
+
+ protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, Dotnet_3597_Old value)
+ {
+ context.Writer.WriteStartDocument();
+
+ WriteValue(context, args, "IntProp", value.IntProp);
+ WriteValue(context, args, "FloatProp", value.FloatProp);
+ WriteList(context, args, "FloatList", value.FloatList);
+
+ context.Writer.WriteEndDocument();
+ }
+
+ protected override Dotnet_3597_Old CreateInstance() => new Dotnet_3597_Old();
+
+ protected override void ReadValue(Dotnet_3597_Old instance, string name, BsonDeserializationContext context)
+ {
+ switch (name)
+ {
+ case "IntProp":
+ instance.IntProp = BsonSerializer.LookupSerializer().Deserialize(context);
+ break;
+ case "FloatProp":
+ instance.FloatProp = BsonSerializer.LookupSerializer().Deserialize(context);
+ break;
+ case "FloatList":
+ ReadArray(instance, name, context);
+ break;
+ default:
+ context.Reader.SkipValue();
+ break;
+ }
+ }
+
+ protected override void ReadArrayElement(Dotnet_3597_Old instance, string name, BsonDeserializationContext context)
+ {
+ switch (name)
+ {
+ case "FloatList":
+ instance.FloatList.Add(BsonSerializer.LookupSerializer().Deserialize(context));
+ break;
+ }
+ }
+
+ protected override void ReadDocumentField(Dotnet_3597_Old instance, string name, string fieldName, BsonDeserializationContext context)
+ {
+ // No persisted dictionary properties to deserialize
+ }
+ }
+ }
+}
diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_generated.cs
new file mode 100644
index 0000000000..eca00f0a47
--- /dev/null
+++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/Dotnet_3597_generated.cs
@@ -0,0 +1,446 @@
+//
+#nullable enable
+
+using MongoDB.Bson.Serialization;
+using NUnit.Framework;
+using Realms;
+using Realms.Exceptions;
+using Realms.Extensions;
+using Realms.Schema;
+using Realms.Tests.Database;
+using Realms.Weaving;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Xml.Serialization;
+using TestEmbeddedObject = Realms.IEmbeddedObject;
+using TestRealmObject = Realms.IRealmObject;
+
+namespace Realms.Tests.Database
+{
+ [Generated]
+ [Woven(typeof(Dotnet_3597ObjectHelper)), Realms.Preserve(AllMembers = true)]
+ public partial class Dotnet_3597 : IRealmObject, INotifyPropertyChanged, IReflectableType
+ {
+
+ [Realms.Preserve]
+ static Dotnet_3597()
+ {
+ Realms.Serialization.RealmObjectSerializer.Register(new Dotnet_3597Serializer());
+ }
+
+ ///
+ /// Defines the schema for the class.
+ ///
+ public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("Dotnet_3597", ObjectSchema.ObjectType.RealmObject)
+ {
+ Realms.Schema.Property.Primitive("IntProp", Realms.RealmValueType.Int, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "IntProp"),
+ Realms.Schema.Property.Primitive("FloatProp", Realms.RealmValueType.String, isPrimaryKey: false, indexType: IndexType.None, isNullable: false, managedName: "FloatProp"),
+ Realms.Schema.Property.PrimitiveList("FloatList", Realms.RealmValueType.String, areElementsNullable: false, managedName: "FloatList"),
+ }.Build();
+
+ #region IRealmObject implementation
+
+ private IDotnet_3597Accessor? _accessor;
+
+ Realms.IRealmAccessor Realms.IRealmObjectBase.Accessor => Accessor;
+
+ private IDotnet_3597Accessor Accessor => _accessor ??= new Dotnet_3597UnmanagedAccessor(typeof(Dotnet_3597));
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsManaged => Accessor.IsManaged;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsValid => Accessor.IsValid;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public bool IsFrozen => Accessor.IsFrozen;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.Realm? Realm => Accessor.Realm;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi;
+
+ ///
+ [IgnoreDataMember, XmlIgnore]
+ public int BacklinksCount => Accessor.BacklinksCount;
+
+ void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults)
+ {
+ var newAccessor = (IDotnet_3597Accessor)managedAccessor;
+ var oldAccessor = _accessor;
+ _accessor = newAccessor;
+
+ if (helper != null && oldAccessor != null)
+ {
+ if (!skipDefaults)
+ {
+ newAccessor.FloatList.Clear();
+ }
+
+ if (!skipDefaults || oldAccessor.IntProp != default(int))
+ {
+ newAccessor.IntProp = oldAccessor.IntProp;
+ }
+ newAccessor.FloatProp = oldAccessor.FloatProp;
+ Realms.CollectionExtensions.PopulateCollection(oldAccessor.FloatList, newAccessor.FloatList, update, skipDefaults);
+ }
+
+ if (_propertyChanged != null)
+ {
+ SubscribeForNotifications();
+ }
+
+ OnManaged();
+ }
+
+ #endregion
+
+ ///
+ /// Called when the object has been managed by a Realm.
+ ///
+ ///
+ /// This method will be called either when a managed object is materialized or when an unmanaged object has been
+ /// added to the Realm. It can be useful for providing some initialization logic as when the constructor is invoked,
+ /// it is not yet clear whether the object is managed or not.
+ ///
+ partial void OnManaged();
+
+ private event PropertyChangedEventHandler? _propertyChanged;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged
+ {
+ add
+ {
+ if (_propertyChanged == null)
+ {
+ SubscribeForNotifications();
+ }
+
+ _propertyChanged += value;
+ }
+
+ remove
+ {
+ _propertyChanged -= value;
+
+ if (_propertyChanged == null)
+ {
+ UnsubscribeFromNotifications();
+ }
+ }
+ }
+
+ ///
+ /// Called when a property has changed on this class.
+ ///
+ /// The name of the property.
+ ///
+ /// For this method to be called, you need to have first subscribed to .
+ /// This can be used to react to changes to the current object, e.g. raising for computed properties.
+ ///
+ ///
+ ///
+ /// class MyClass : IRealmObject
+ /// {
+ /// public int StatusCodeRaw { get; set; }
+ /// public StatusCodeEnum StatusCode => (StatusCodeEnum)StatusCodeRaw;
+ /// partial void OnPropertyChanged(string propertyName)
+ /// {
+ /// if (propertyName == nameof(StatusCodeRaw))
+ /// {
+ /// RaisePropertyChanged(nameof(StatusCode));
+ /// }
+ /// }
+ /// }
+ ///
+ /// Here, we have a computed property that depends on a persisted one. In order to notify any
+ /// subscribers that StatusCode has changed, we implement and
+ /// raise manually by calling .
+ ///
+ partial void OnPropertyChanged(string? propertyName);
+
+ private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ OnPropertyChanged(propertyName);
+ }
+
+ private void SubscribeForNotifications()
+ {
+ Accessor.SubscribeForNotifications(RaisePropertyChanged);
+ }
+
+ private void UnsubscribeFromNotifications()
+ {
+ Accessor.UnsubscribeFromNotifications();
+ }
+
+ ///
+ /// Converts a to . Equivalent to .
+ ///
+ /// The to convert.
+ /// The stored in the .
+ public static explicit operator Dotnet_3597?(Realms.RealmValue val) => val.Type == Realms.RealmValueType.Null ? null : val.AsRealmObject();
+
+ ///
+ /// Implicitly constructs a from .
+ ///
+ /// The value to store in the .
+ /// A containing the supplied .
+ public static implicit operator Realms.RealmValue(Dotnet_3597? val) => val == null ? Realms.RealmValue.Null : Realms.RealmValue.Object(val);
+
+ ///
+ /// Implicitly constructs a from .
+ ///
+ /// The value to store in the .
+ /// A containing the supplied .
+ public static implicit operator Realms.QueryArgument(Dotnet_3597? val) => (Realms.RealmValue)val;
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public TypeInfo GetTypeInfo() => Accessor.GetTypeInfo(this);
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (obj is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj is InvalidObject)
+ {
+ return !IsValid;
+ }
+
+ if (!(obj is Realms.IRealmObjectBase iro))
+ {
+ return false;
+ }
+
+ return Accessor.Equals(iro.Accessor);
+ }
+
+ ///
+ public override int GetHashCode() => IsManaged ? Accessor.GetHashCode() : base.GetHashCode();
+
+ ///
+ public override string? ToString() => Accessor.ToString();
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597ObjectHelper : Realms.Weaving.IRealmObjectHelper
+ {
+ public void CopyToRealm(Realms.IRealmObjectBase instance, bool update, bool skipDefaults)
+ {
+ throw new InvalidOperationException("This method should not be called for source generated classes.");
+ }
+
+ public Realms.ManagedAccessor CreateAccessor() => new Dotnet_3597ManagedAccessor();
+
+ public Realms.IRealmObjectBase CreateInstance() => new Dotnet_3597();
+
+ public bool TryGetPrimaryKeyValue(Realms.IRealmObjectBase instance, out RealmValue value)
+ {
+ value = RealmValue.Null;
+ return false;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ internal interface IDotnet_3597Accessor : Realms.IRealmAccessor
+ {
+ int IntProp { get; set; }
+
+ string FloatProp { get; set; }
+
+ System.Collections.Generic.IList FloatList { get; }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597ManagedAccessor : Realms.ManagedAccessor, IDotnet_3597Accessor
+ {
+ public int IntProp
+ {
+ get => (int)GetValue("IntProp");
+ set => SetValue("IntProp", value);
+ }
+
+ public string FloatProp
+ {
+ get => (string)GetValue("FloatProp")!;
+ set => SetValue("FloatProp", value);
+ }
+
+ private System.Collections.Generic.IList _floatList = null!;
+ public System.Collections.Generic.IList FloatList
+ {
+ get
+ {
+ if (_floatList == null)
+ {
+ _floatList = GetListValue("FloatList");
+ }
+
+ return _floatList;
+ }
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597UnmanagedAccessor : Realms.UnmanagedAccessor, IDotnet_3597Accessor
+ {
+ public override ObjectSchema ObjectSchema => Dotnet_3597.RealmSchema;
+
+ private int _intProp;
+ public int IntProp
+ {
+ get => _intProp;
+ set
+ {
+ _intProp = value;
+ RaisePropertyChanged("IntProp");
+ }
+ }
+
+ private string _floatProp = null!;
+ public string FloatProp
+ {
+ get => _floatProp;
+ set
+ {
+ _floatProp = value;
+ RaisePropertyChanged("FloatProp");
+ }
+ }
+
+ public System.Collections.Generic.IList FloatList { get; } = new List();
+
+ public Dotnet_3597UnmanagedAccessor(Type objectType) : base(objectType)
+ {
+ }
+
+ public override Realms.RealmValue GetValue(string propertyName)
+ {
+ return propertyName switch
+ {
+ "IntProp" => _intProp,
+ "FloatProp" => _floatProp,
+ _ => throw new MissingMemberException($"The object does not have a gettable Realm property with name {propertyName}"),
+ };
+ }
+
+ public override void SetValue(string propertyName, Realms.RealmValue val)
+ {
+ switch (propertyName)
+ {
+ case "IntProp":
+ IntProp = (int)val;
+ return;
+ case "FloatProp":
+ FloatProp = (string)val!;
+ return;
+ default:
+ throw new MissingMemberException($"The object does not have a settable Realm property with name {propertyName}");
+ }
+ }
+
+ public override void SetValueUnique(string propertyName, Realms.RealmValue val)
+ {
+ throw new InvalidOperationException("Cannot set the value of an non primary key property with SetValueUnique");
+ }
+
+ public override IList GetListValue(string propertyName)
+ {
+ return propertyName switch
+ {
+ "FloatList" => (IList)FloatList,
+ _ => throw new MissingMemberException($"The object does not have a Realm list property with name {propertyName}"),
+ };
+ }
+
+ public override ISet GetSetValue(string propertyName)
+ {
+ throw new MissingMemberException($"The object does not have a Realm set property with name {propertyName}");
+ }
+
+ public override IDictionary GetDictionaryValue(string propertyName)
+ {
+ throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}");
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
+ private class Dotnet_3597Serializer : Realms.Serialization.RealmObjectSerializerBase
+ {
+ public override string SchemaName => "Dotnet_3597";
+
+ protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, Dotnet_3597 value)
+ {
+ context.Writer.WriteStartDocument();
+
+ WriteValue(context, args, "IntProp", value.IntProp);
+ WriteValue(context, args, "FloatProp", value.FloatProp);
+ WriteList(context, args, "FloatList", value.FloatList);
+
+ context.Writer.WriteEndDocument();
+ }
+
+ protected override Dotnet_3597 CreateInstance() => new Dotnet_3597();
+
+ protected override void ReadValue(Dotnet_3597 instance, string name, BsonDeserializationContext context)
+ {
+ switch (name)
+ {
+ case "IntProp":
+ instance.IntProp = BsonSerializer.LookupSerializer().Deserialize(context);
+ break;
+ case "FloatProp":
+ instance.FloatProp = BsonSerializer.LookupSerializer().Deserialize(context);
+ break;
+ case "FloatList":
+ ReadArray(instance, name, context);
+ break;
+ default:
+ context.Reader.SkipValue();
+ break;
+ }
+ }
+
+ protected override void ReadArrayElement(Dotnet_3597 instance, string name, BsonDeserializationContext context)
+ {
+ switch (name)
+ {
+ case "FloatList":
+ instance.FloatList.Add(BsonSerializer.LookupSerializer().Deserialize(context));
+ break;
+ }
+ }
+
+ protected override void ReadDocumentField(Dotnet_3597 instance, string name, string fieldName, BsonDeserializationContext context)
+ {
+ // No persisted dictionary properties to deserialize
+ }
+ }
+ }
+}
diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp
index 25661bfc71..8b1d6b9061 100644
--- a/wrappers/src/shared_realm_cs.cpp
+++ b/wrappers/src/shared_realm_cs.cpp
@@ -693,6 +693,21 @@ REALM_EXPORT Object* shared_realm_get_object_for_primary_key(SharedRealm& realm,
});
}
+REALM_EXPORT Object* shared_realm_get_object_for_object(SharedRealm& realm, Object& object, NativeException::Marshallable& ex)
+{
+ return handle_errors(ex, [&]() -> Object* {
+ realm->verify_thread();
+
+ auto table = realm->read_group().get_table(object.get_object_schema().table_key);
+ auto obj = table->try_get_object(object.get_obj().get_key());
+ if (!obj) {
+ return nullptr;
+ }
+
+ return new Object(realm, std::move(obj));
+ });
+}
+
REALM_EXPORT Results* shared_realm_create_results(SharedRealm& realm, TableKey table_key, NativeException::Marshallable& ex)
{
return handle_errors(ex, [&]() {