From 03c48d753efa16255bfc1892cd649b50969472df Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:56:18 +0100 Subject: [PATCH 01/10] Added support for expressions in keypath building --- .../Realm/DatabaseTypes/KeyPathCollection.cs | 51 +++++++++++++++++ .../Realm.Tests/Database/NotificationTests.cs | 33 +++++++++++ .../DeepObject1_generated.cs | 56 ++++++++++++++++++- .../DeepObject2_generated.cs | 56 ++++++++++++++++++- .../DeepObject3_generated.cs | 56 ++++++++++++++++++- .../DeepObject4_generated.cs | 56 ++++++++++++++++++- .../DeepObject5_generated.cs | 52 ++++++++++++++++- .../TestNotificationObject_generated.cs | 23 ++++++++ 8 files changed, 373 insertions(+), 10 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index d33aef4f93..9fa8f9a42d 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; namespace Realms; @@ -78,6 +79,19 @@ public static KeyPathsCollection Of(params KeyPath[] paths) return new KeyPathsCollection(KeyPathsCollectionType.Explicit, paths); } + /// + /// Builds a from an array of . + /// Each of the expressions must represent the path to a realm object property, eventually chained. + /// + /// The realm object. type. + /// The array of to use for the . + /// The built from the input array of . + public static KeyPathsCollection Of(params Expression>[] expressions) + where T : IRealmObject + { + return Of(expressions.Select(KeyPath.For).ToArray()); + } + /// /// Gets a value for shallow notifications, that will raise notifications only for changes to the collection itself (for example when an element is added or removed), /// but not for changes to any of the properties of the elements of the collection. @@ -125,10 +139,14 @@ IEnumerator IEnumerable.GetEnumerator() } } +//TODO Fix changelog + /// /// Represents a key path that can be used as a part of a when subscribing for notifications. /// A can be implicitly built from a string, where the string is the name of a property (e.g "FirstName"), eventually dotted to indicated nested properties. /// (e.g "Dog.Name"). Wildcards can also be used in key paths to capture all properties at a given level (e.g "*", "Friends.*" or "*.FirstName"). +/// A can also be built using the method, that creates the corresponding +/// to the property path represented by the input expression. /// public readonly struct KeyPath { @@ -139,8 +157,41 @@ private KeyPath(string path) Path = path; } + /// + /// Creates a from a lambda expression that specifies a property path for a given realm object type. + /// + /// The type of the realm object.. + /// The expression specifying the path to the property. + /// A representing the full path to the specified property. + /// + /// + /// var keyPath = KeyPath.For<Person>(p => p.Dog.Name); + /// + /// + public static KeyPath For(Expression> expression) + where T : IRealmObject + { + return new KeyPath(GetFullPath(expression.Body)); + } + public static implicit operator KeyPath(string s) => new(s); + private static string GetFullPath(Expression expression) + { + if (expression is MemberExpression memberExpression) + { + var subPath = GetFullPath(memberExpression.Expression); + return string.IsNullOrEmpty(subPath) ? memberExpression.Member.Name : $"{subPath}.{memberExpression.Member.Name}"; + } + else if (expression is ParameterExpression) + { + // This is the parameter of the expression, nothing to add + return string.Empty; + } + + throw new Exception(); + } + /// public override bool Equals(object? obj) => obj is KeyPath path && Path == path.Path; diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index 7a75955ebe..22dda2007d 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1654,6 +1654,18 @@ public void KeyPath_ImplicitOperator_CorrectlyConvertsFromString() Assert.That(keyPath.Path, Is.EqualTo("test")); } + [Test] + public void KeyPath_CanBeBuiltFromExpressions() + { + KeyPath keyPath; + + keyPath = KeyPath.For(t => t.ListSameType); + Assert.That(keyPath.Path, Is.EqualTo("ListSameType")); + + keyPath = KeyPath.For(t => t.LinkAnotherType!.DictOfDogs); + Assert.That(keyPath.Path, Is.EqualTo("LinkAnotherType.DictOfDogs")); + } + [Test] public void KeyPathsCollection_CanBeBuiltInDifferentWays() { @@ -1710,6 +1722,27 @@ void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable { "ListSameType", "LinkAnotherType.DictOfDogs" }; + + kpc = KeyPathsCollection.Of(t => t.ListSameType, t => t.LinkAnotherType!.DictOfDogs); + AssertKeyPathsCollectionCorrectness(kpc, expected); + + kpc = KeyPathsCollection.Of(KeyPath.For(t => t.ListSameType), + KeyPath.For(t => t.LinkAnotherType!.DictOfDogs)); + AssertKeyPathsCollectionCorrectness(kpc, expected); + + void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable expected) + { + Assert.That(k.Type, Is.EqualTo(KeyPathsCollectionType.Explicit)); + Assert.That(k.GetStrings(), Is.EqualTo(expected)); + } + } + [Test] public void SubscribeWithKeypaths_AnyKeypath_RaisesNotificationsForResults() { diff --git a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject1_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject1_generated.cs index ce4427b07e..40a88b97d8 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject1_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject1_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using NUnit.Framework; using Realms; using Realms.Logging; @@ -26,6 +27,13 @@ namespace Realms.Tests.Database [Woven(typeof(DeepObject1ObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class DeepObject1 : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static DeepObject1() + { + Realms.Serialization.RealmObjectSerializer.Register(new DeepObject1Serializer()); + } + /// /// Defines the schema for the class. /// @@ -266,7 +274,7 @@ internal interface IDeepObject1Accessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject1ManagedAccessor : Realms.ManagedAccessor, IDeepObject1Accessor + private class DeepObject1ManagedAccessor : Realms.ManagedAccessor, IDeepObject1Accessor { public string? StringValue { @@ -282,7 +290,7 @@ public Realms.Tests.Database.DeepObject2? RecursiveObject } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject1UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject1Accessor + private class DeepObject1UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject1Accessor { public override ObjectSchema ObjectSchema => DeepObject1.RealmSchema; @@ -357,5 +365,49 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class DeepObject1Serializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "DeepObject1"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, DeepObject1 value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "StringValue", value.StringValue); + WriteValue(context, args, "RecursiveObject", value.RecursiveObject); + + context.Writer.WriteEndDocument(); + } + + protected override DeepObject1 CreateInstance() => new DeepObject1(); + + protected override void ReadValue(DeepObject1 instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "StringValue": + instance.StringValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + case "RecursiveObject": + instance.RecursiveObject = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(DeepObject1 instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(DeepObject1 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/DeepObject2_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject2_generated.cs index babdbb7bd9..d97dccd7d1 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject2_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject2_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using NUnit.Framework; using Realms; using Realms.Logging; @@ -26,6 +27,13 @@ namespace Realms.Tests.Database [Woven(typeof(DeepObject2ObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class DeepObject2 : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static DeepObject2() + { + Realms.Serialization.RealmObjectSerializer.Register(new DeepObject2Serializer()); + } + /// /// Defines the schema for the class. /// @@ -266,7 +274,7 @@ internal interface IDeepObject2Accessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject2ManagedAccessor : Realms.ManagedAccessor, IDeepObject2Accessor + private class DeepObject2ManagedAccessor : Realms.ManagedAccessor, IDeepObject2Accessor { public string? StringValue { @@ -282,7 +290,7 @@ public Realms.Tests.Database.DeepObject3? RecursiveObject } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject2UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject2Accessor + private class DeepObject2UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject2Accessor { public override ObjectSchema ObjectSchema => DeepObject2.RealmSchema; @@ -357,5 +365,49 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class DeepObject2Serializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "DeepObject2"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, DeepObject2 value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "StringValue", value.StringValue); + WriteValue(context, args, "RecursiveObject", value.RecursiveObject); + + context.Writer.WriteEndDocument(); + } + + protected override DeepObject2 CreateInstance() => new DeepObject2(); + + protected override void ReadValue(DeepObject2 instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "StringValue": + instance.StringValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + case "RecursiveObject": + instance.RecursiveObject = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(DeepObject2 instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(DeepObject2 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/DeepObject3_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject3_generated.cs index 8207c59534..e252614c41 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject3_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject3_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using NUnit.Framework; using Realms; using Realms.Logging; @@ -26,6 +27,13 @@ namespace Realms.Tests.Database [Woven(typeof(DeepObject3ObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class DeepObject3 : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static DeepObject3() + { + Realms.Serialization.RealmObjectSerializer.Register(new DeepObject3Serializer()); + } + /// /// Defines the schema for the class. /// @@ -266,7 +274,7 @@ internal interface IDeepObject3Accessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject3ManagedAccessor : Realms.ManagedAccessor, IDeepObject3Accessor + private class DeepObject3ManagedAccessor : Realms.ManagedAccessor, IDeepObject3Accessor { public string? StringValue { @@ -282,7 +290,7 @@ public Realms.Tests.Database.DeepObject4? RecursiveObject } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject3UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject3Accessor + private class DeepObject3UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject3Accessor { public override ObjectSchema ObjectSchema => DeepObject3.RealmSchema; @@ -357,5 +365,49 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class DeepObject3Serializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "DeepObject3"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, DeepObject3 value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "StringValue", value.StringValue); + WriteValue(context, args, "RecursiveObject", value.RecursiveObject); + + context.Writer.WriteEndDocument(); + } + + protected override DeepObject3 CreateInstance() => new DeepObject3(); + + protected override void ReadValue(DeepObject3 instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "StringValue": + instance.StringValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + case "RecursiveObject": + instance.RecursiveObject = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(DeepObject3 instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(DeepObject3 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/DeepObject4_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject4_generated.cs index 64be4c5bab..949b186836 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject4_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject4_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using NUnit.Framework; using Realms; using Realms.Logging; @@ -26,6 +27,13 @@ namespace Realms.Tests.Database [Woven(typeof(DeepObject4ObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class DeepObject4 : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static DeepObject4() + { + Realms.Serialization.RealmObjectSerializer.Register(new DeepObject4Serializer()); + } + /// /// Defines the schema for the class. /// @@ -266,7 +274,7 @@ internal interface IDeepObject4Accessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject4ManagedAccessor : Realms.ManagedAccessor, IDeepObject4Accessor + private class DeepObject4ManagedAccessor : Realms.ManagedAccessor, IDeepObject4Accessor { public string? StringValue { @@ -282,7 +290,7 @@ public Realms.Tests.Database.DeepObject5? RecursiveObject } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject4UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject4Accessor + private class DeepObject4UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject4Accessor { public override ObjectSchema ObjectSchema => DeepObject4.RealmSchema; @@ -357,5 +365,49 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class DeepObject4Serializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "DeepObject4"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, DeepObject4 value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "StringValue", value.StringValue); + WriteValue(context, args, "RecursiveObject", value.RecursiveObject); + + context.Writer.WriteEndDocument(); + } + + protected override DeepObject4 CreateInstance() => new DeepObject4(); + + protected override void ReadValue(DeepObject4 instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "StringValue": + instance.StringValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + case "RecursiveObject": + instance.RecursiveObject = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(DeepObject4 instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(DeepObject4 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/DeepObject5_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject5_generated.cs index 87ec57eb67..c92b2a4f6a 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject5_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/DeepObject5_generated.cs @@ -1,6 +1,7 @@ // #nullable enable +using MongoDB.Bson.Serialization; using NUnit.Framework; using Realms; using Realms.Logging; @@ -26,6 +27,13 @@ namespace Realms.Tests.Database [Woven(typeof(DeepObject5ObjectHelper)), Realms.Preserve(AllMembers = true)] public partial class DeepObject5 : IRealmObject, INotifyPropertyChanged, IReflectableType { + + [Realms.Preserve] + static DeepObject5() + { + Realms.Serialization.RealmObjectSerializer.Register(new DeepObject5Serializer()); + } + /// /// Defines the schema for the class. /// @@ -258,7 +266,7 @@ internal interface IDeepObject5Accessor : Realms.IRealmAccessor } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject5ManagedAccessor : Realms.ManagedAccessor, IDeepObject5Accessor + private class DeepObject5ManagedAccessor : Realms.ManagedAccessor, IDeepObject5Accessor { public string? StringValue { @@ -268,7 +276,7 @@ public string? StringValue } [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] - internal class DeepObject5UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject5Accessor + private class DeepObject5UnmanagedAccessor : Realms.UnmanagedAccessor, IDeepObject5Accessor { public override ObjectSchema ObjectSchema => DeepObject5.RealmSchema; @@ -328,5 +336,45 @@ public override IDictionary GetDictionaryValue(string pr throw new MissingMemberException($"The object does not have a Realm dictionary property with name {propertyName}"); } } + + [EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)] + private class DeepObject5Serializer : Realms.Serialization.RealmObjectSerializerBase + { + public override string SchemaName => "DeepObject5"; + + protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, DeepObject5 value) + { + context.Writer.WriteStartDocument(); + + WriteValue(context, args, "StringValue", value.StringValue); + + context.Writer.WriteEndDocument(); + } + + protected override DeepObject5 CreateInstance() => new DeepObject5(); + + protected override void ReadValue(DeepObject5 instance, string name, BsonDeserializationContext context) + { + switch (name) + { + case "StringValue": + instance.StringValue = BsonSerializer.LookupSerializer().Deserialize(context); + break; + default: + context.Reader.SkipValue(); + break; + } + } + + protected override void ReadArrayElement(DeepObject5 instance, string name, BsonDeserializationContext context) + { + // No persisted list/set properties to deserialize + } + + protected override void ReadDocumentField(DeepObject5 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/TestNotificationObject_generated.cs b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/TestNotificationObject_generated.cs index 19e1eec1cd..7c6833aa97 100644 --- a/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/TestNotificationObject_generated.cs +++ b/Tests/Realm.Tests/Generated/Realm.SourceGenerator/Realms.SourceGenerator.RealmGenerator/TestNotificationObject_generated.cs @@ -688,6 +688,7 @@ protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializat context.Writer.WriteStartDocument(); WriteValue(context, args, "StringProperty", value.StringProperty); + WriteValue(context, args, "IntProperty", value.IntProperty); WriteList(context, args, "ListSameType", value.ListSameType); WriteSet(context, args, "SetSameType", value.SetSameType); WriteDictionary(context, args, "DictionarySameType", value.DictionarySameType); @@ -695,7 +696,11 @@ protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializat WriteList(context, args, "ListDifferentType", value.ListDifferentType); WriteSet(context, args, "SetDifferentType", value.SetDifferentType); WriteDictionary(context, args, "DictionaryDifferentType", value.DictionaryDifferentType); + WriteList(context, args, "ListRemappedType", value.ListRemappedType); + WriteSet(context, args, "SetRemappedType", value.SetRemappedType); + WriteDictionary(context, args, "DictionaryRemappedType", value.DictionaryRemappedType); WriteValue(context, args, "LinkDifferentType", value.LinkDifferentType); + WriteValue(context, args, "LinkAnotherType", value.LinkAnotherType); context.Writer.WriteEndDocument(); } @@ -709,20 +714,29 @@ protected override void ReadValue(TestNotificationObject instance, string name, case "StringProperty": instance.StringProperty = BsonSerializer.LookupSerializer().Deserialize(context); break; + case "IntProperty": + instance.IntProperty = BsonSerializer.LookupSerializer().Deserialize(context); + break; case "LinkSameType": instance.LinkSameType = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); break; case "LinkDifferentType": instance.LinkDifferentType = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); break; + case "LinkAnotherType": + instance.LinkAnotherType = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context); + break; case "ListSameType": case "SetSameType": case "ListDifferentType": case "SetDifferentType": + case "ListRemappedType": + case "SetRemappedType": ReadArray(instance, name, context); break; case "DictionarySameType": case "DictionaryDifferentType": + case "DictionaryRemappedType": ReadDictionary(instance, name, context); break; default: @@ -747,6 +761,12 @@ protected override void ReadArrayElement(TestNotificationObject instance, string case "SetDifferentType": instance.SetDifferentType.Add(Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!); break; + case "ListRemappedType": + instance.ListRemappedType.Add(Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!); + break; + case "SetRemappedType": + instance.SetRemappedType.Add(Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!); + break; } } @@ -760,6 +780,9 @@ protected override void ReadDocumentField(TestNotificationObject instance, strin case "DictionaryDifferentType": instance.DictionaryDifferentType[fieldName] = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!; break; + case "DictionaryRemappedType": + instance.DictionaryRemappedType[fieldName] = Realms.Serialization.RealmObjectSerializer.LookupSerializer()!.DeserializeById(context)!; + break; } } } From 4052fdc831b12892a9d1549733f8116f010e2c4e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:22:53 +0100 Subject: [PATCH 02/10] Added more tests --- .../Realm/DatabaseTypes/KeyPathCollection.cs | 12 +++++++++-- .../Realm.Tests/Database/NotificationTests.cs | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index 9fa8f9a42d..7dc3de04b5 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -22,6 +22,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace Realms; @@ -171,6 +172,11 @@ private KeyPath(string path) public static KeyPath For(Expression> expression) where T : IRealmObject { + if (expression is null) + { + throw new ArgumentException("The input expression cannot be null"); + } + return new KeyPath(GetFullPath(expression.Body)); } @@ -178,7 +184,9 @@ public static KeyPath For(Expression> expression) private static string GetFullPath(Expression expression) { - if (expression is MemberExpression memberExpression) + if (expression is MemberExpression memberExpression // Either field or property expression + && memberExpression.Expression is not null // Filtering out static members + && memberExpression.Member is PropertyInfo) // Filtering for property expressions only { var subPath = GetFullPath(memberExpression.Expression); return string.IsNullOrEmpty(subPath) ? memberExpression.Member.Name : $"{subPath}.{memberExpression.Member.Name}"; @@ -189,7 +197,7 @@ private static string GetFullPath(Expression expression) return string.Empty; } - throw new Exception(); + throw new ArgumentException("The input expression is not a path to a property"); } /// diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index 22dda2007d..f9c6da9335 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1666,6 +1666,16 @@ public void KeyPath_CanBeBuiltFromExpressions() Assert.That(keyPath.Path, Is.EqualTo("LinkAnotherType.DictOfDogs")); } + [Test] + public void KeyPath_WithInvalidExpressions_ThrowsException() + { + Assert.That(() => KeyPath.For(t => t.Equals(this)), + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); + + Assert.That(() => KeyPath.For(null!), + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null")); + } + [Test] public void KeyPathsCollection_CanBeBuiltInDifferentWays() { @@ -1743,6 +1753,16 @@ void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable KeyPathsCollection.Of(t => t.ListSameType, null!), + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null")); + + Assert.That(() => KeyPathsCollection.Of(t => t.Equals(this)), + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); + } + [Test] public void SubscribeWithKeypaths_AnyKeypath_RaisesNotificationsForResults() { From 8403227e876d9c6b82fbd52b41cf1458549f34fd Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:31:34 +0100 Subject: [PATCH 03/10] Fixed docs --- Realm/Realm/DatabaseTypes/KeyPathCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index 7dc3de04b5..5b43a455a8 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -159,7 +159,7 @@ private KeyPath(string path) } /// - /// Creates a from a lambda expression that specifies a property path for a given realm object type. + /// Creates a from an that specifies a property path for a given realm object type. /// /// The type of the realm object.. /// The expression specifying the path to the property. From 00605950f2e9b6a3a3d7bf111ae6ee32ef3c5853 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:35:15 +0100 Subject: [PATCH 04/10] Fixed changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de0045eeaf..a4fc8900d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,10 @@ * Add support for passing a key paths collection (`KeyPathsCollection`) when using `IRealmCollection.SubscribeForNotifications`. Passing a `KeyPathsCollection` allows to specify which changes in properties should raise a notification. A `KeyPathsCollection` can be obtained by: - - building it explicitly by using the method `KeyPathsCollection.Of`; + - building it explicitly by using the methods `KeyPathsCollection.Of` or `KeyPathsCollection.Of`; - building it implicitly with the conversion from a `List` or array of `KeyPath` or strings; - getting one of the static values `Full` and `Shallow` for full and shallow notifications respectively. - For example: ```csharp var query = realm.All(); @@ -20,6 +19,7 @@ //Equivalent declarations kpc = KeyPathsCollection.Of("Email", "Name"); + kpc = KeyPathsCollection.Of(p => p.Email, p => p.Name); kpc = new List {"Email", "Name"}; query.SubscribeForNotifications(NotificationCallback, kpc); From 48da0150b63252592e641b0ac2079e8750f104a4 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:11:00 +0100 Subject: [PATCH 05/10] Small fix --- Realm/Realm/DatabaseTypes/KeyPathCollection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index 5b43a455a8..6311b70d93 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -184,9 +184,9 @@ public static KeyPath For(Expression> expression) private static string GetFullPath(Expression expression) { - if (expression is MemberExpression memberExpression // Either field or property expression - && memberExpression.Expression is not null // Filtering out static members - && memberExpression.Member is PropertyInfo) // Filtering for property expressions only + if (expression is MemberExpression memberExpression //// Either field or property expression + && memberExpression.Expression is not null //// Filtering out static members + && memberExpression.Member is PropertyInfo) //// Filtering for property expressions only { var subPath = GetFullPath(memberExpression.Expression); return string.IsNullOrEmpty(subPath) ? memberExpression.Member.Name : $"{subPath}.{memberExpression.Member.Name}"; From 378d099fdf0834c6f7ba9b0bd34b420c057d214e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:03:12 +0100 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Nikola Irinchev --- Realm/Realm/DatabaseTypes/KeyPathCollection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index 6311b70d93..497a70814c 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -87,7 +87,7 @@ public static KeyPathsCollection Of(params KeyPath[] paths) /// The realm object. type. /// The array of to use for the . /// The built from the input array of . - public static KeyPathsCollection Of(params Expression>[] expressions) + public static KeyPathsCollection Of(params Expression>[] expressions) where T : IRealmObject { return Of(expressions.Select(KeyPath.For).ToArray()); @@ -169,15 +169,15 @@ private KeyPath(string path) /// var keyPath = KeyPath.For<Person>(p => p.Dog.Name); /// /// - public static KeyPath For(Expression> expression) + public static KeyPath For(Expression> expression) where T : IRealmObject { if (expression is null) { - throw new ArgumentException("The input expression cannot be null"); + throw new ArgumentException("The input expression cannot be null", nameof(expression)); } - return new KeyPath(GetFullPath(expression.Body)); + return new(GetFullPath(expression.Body)); } public static implicit operator KeyPath(string s) => new(s); From 0823a3829508bca47dc39399bced6ce69cc34997 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:27:58 +0100 Subject: [PATCH 07/10] Corrections according to PR --- .../Realm/DatabaseTypes/KeyPathCollection.cs | 22 ++++++++----------- .../Realm.Tests/Database/NotificationTests.cs | 8 +++---- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index 6311b70d93..7575af57ff 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -184,20 +184,16 @@ public static KeyPath For(Expression> expression) private static string GetFullPath(Expression expression) { - if (expression is MemberExpression memberExpression //// Either field or property expression - && memberExpression.Expression is not null //// Filtering out static members - && memberExpression.Member is PropertyInfo) //// Filtering for property expressions only + return expression switch { - var subPath = GetFullPath(memberExpression.Expression); - return string.IsNullOrEmpty(subPath) ? memberExpression.Member.Name : $"{subPath}.{memberExpression.Member.Name}"; - } - else if (expression is ParameterExpression) - { - // This is the parameter of the expression, nothing to add - return string.Empty; - } - - throw new ArgumentException("The input expression is not a path to a property"); + // MemberExpression: field or property expression; + // Expression == null for static members; + // Member: PropertyInfo to filter out field access + MemberExpression { Expression: { } innerExpression, Member: PropertyInfo pi } => + innerExpression is ParameterExpression ? pi.Name : $"{GetFullPath(innerExpression)}.{pi.Name}", + ParameterExpression => string.Empty, + _ => throw new ArgumentException("The input expression is not a path to a property"), + }; } /// diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index f9c6da9335..770d79a8f9 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1735,21 +1735,19 @@ void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable { "ListSameType", "LinkAnotherType.DictOfDogs" }; - kpc = KeyPathsCollection.Of(t => t.ListSameType, t => t.LinkAnotherType!.DictOfDogs); + var kpc = KeyPathsCollection.Of(t => t.ListSameType, t => t.LinkAnotherType!.DictOfDogs); AssertKeyPathsCollectionCorrectness(kpc, expected); kpc = KeyPathsCollection.Of(KeyPath.For(t => t.ListSameType), KeyPath.For(t => t.LinkAnotherType!.DictOfDogs)); AssertKeyPathsCollectionCorrectness(kpc, expected); - void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable expected) + void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable expectedStrings) { Assert.That(k.Type, Is.EqualTo(KeyPathsCollectionType.Explicit)); - Assert.That(k.GetStrings(), Is.EqualTo(expected)); + Assert.That(k.GetStrings(), Is.EqualTo(expectedStrings)); } } From 5f99655cd79c186cd7979071341764ac8c79ebd5 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:38:12 +0100 Subject: [PATCH 08/10] Fixed tests and merged main --- Tests/Realm.Tests/Database/NotificationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index 770d79a8f9..bb7508e4fe 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1673,7 +1673,7 @@ public void KeyPath_WithInvalidExpressions_ThrowsException() Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); Assert.That(() => KeyPath.For(null!), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null")); + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null (Parameter 'expression')")); } [Test] @@ -1755,7 +1755,7 @@ void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable KeyPathsCollection.Of(t => t.ListSameType, null!), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null")); + Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null (Parameter 'expression')")); Assert.That(() => KeyPathsCollection.Of(t => t.Equals(this)), Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); From afd96bb634cd17634dc5e7cd31520e3e8b7920d0 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:11:00 +0100 Subject: [PATCH 09/10] Corrections --- CHANGELOG.md | 6 ++++++ Realm/Realm/DatabaseTypes/KeyPathCollection.cs | 8 +++----- Tests/Realm.Tests/Database/NotificationTests.cs | 12 ++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4fc8900d0..f48e27378a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,16 +11,22 @@ - building it implicitly with the conversion from a `List` or array of `KeyPath` or strings; - getting one of the static values `Full` and `Shallow` for full and shallow notifications respectively. + A `KeyPath` can be obtained by implicit conversion from a string or built from an expression using the `KeyPath.ForExpression` method. + For example: ```csharp var query = realm.All(); + KeyPath kp1 = "Email"; + KeyPath kp2 = KeyPath.ForExpression(p => p.Name); + KeyPathsCollection kpc; //Equivalent declarations kpc = KeyPathsCollection.Of("Email", "Name"); kpc = KeyPathsCollection.Of(p => p.Email, p => p.Name); kpc = new List {"Email", "Name"}; + kpc = new List {kp1, kp2}; query.SubscribeForNotifications(NotificationCallback, kpc); ``` diff --git a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs index ab89f3b68c..57e63ffe80 100644 --- a/Realm/Realm/DatabaseTypes/KeyPathCollection.cs +++ b/Realm/Realm/DatabaseTypes/KeyPathCollection.cs @@ -90,7 +90,7 @@ public static KeyPathsCollection Of(params KeyPath[] paths) public static KeyPathsCollection Of(params Expression>[] expressions) where T : IRealmObject { - return Of(expressions.Select(KeyPath.For).ToArray()); + return Of(expressions.Select(KeyPath.ForExpression).ToArray()); } /// @@ -140,13 +140,11 @@ IEnumerator IEnumerable.GetEnumerator() } } -//TODO Fix changelog - /// /// Represents a key path that can be used as a part of a when subscribing for notifications. /// A can be implicitly built from a string, where the string is the name of a property (e.g "FirstName"), eventually dotted to indicated nested properties. /// (e.g "Dog.Name"). Wildcards can also be used in key paths to capture all properties at a given level (e.g "*", "Friends.*" or "*.FirstName"). -/// A can also be built using the method, that creates the corresponding +/// A can also be built using the method, that creates the corresponding /// to the property path represented by the input expression. /// public readonly struct KeyPath @@ -169,7 +167,7 @@ private KeyPath(string path) /// var keyPath = KeyPath.For<Person>(p => p.Dog.Name); /// /// - public static KeyPath For(Expression> expression) + public static KeyPath ForExpression(Expression> expression) where T : IRealmObject { if (expression is null) diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index bb7508e4fe..8f52d4cb67 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1659,20 +1659,20 @@ public void KeyPath_CanBeBuiltFromExpressions() { KeyPath keyPath; - keyPath = KeyPath.For(t => t.ListSameType); + keyPath = KeyPath.ForExpression(t => t.ListSameType); Assert.That(keyPath.Path, Is.EqualTo("ListSameType")); - keyPath = KeyPath.For(t => t.LinkAnotherType!.DictOfDogs); + keyPath = KeyPath.ForExpression(t => t.LinkAnotherType!.DictOfDogs); Assert.That(keyPath.Path, Is.EqualTo("LinkAnotherType.DictOfDogs")); } [Test] public void KeyPath_WithInvalidExpressions_ThrowsException() { - Assert.That(() => KeyPath.For(t => t.Equals(this)), + Assert.That(() => KeyPath.ForExpression(t => t.Equals(this)), Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); - Assert.That(() => KeyPath.For(null!), + Assert.That(() => KeyPath.ForExpression(null!), Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null (Parameter 'expression')")); } @@ -1740,8 +1740,8 @@ public void KeyPathsCollection_CanBeBuiltFromExpressions() var kpc = KeyPathsCollection.Of(t => t.ListSameType, t => t.LinkAnotherType!.DictOfDogs); AssertKeyPathsCollectionCorrectness(kpc, expected); - kpc = KeyPathsCollection.Of(KeyPath.For(t => t.ListSameType), - KeyPath.For(t => t.LinkAnotherType!.DictOfDogs)); + kpc = KeyPathsCollection.Of(KeyPath.ForExpression(t => t.ListSameType), + KeyPath.ForExpression(t => t.LinkAnotherType!.DictOfDogs)); AssertKeyPathsCollectionCorrectness(kpc, expected); void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable expectedStrings) From 8541a85e43541bfd80fd9df632fd3e6384a827fc Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:21:34 +0100 Subject: [PATCH 10/10] Corrected tests --- Tests/Realm.Tests/Database/NotificationTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Realm.Tests/Database/NotificationTests.cs b/Tests/Realm.Tests/Database/NotificationTests.cs index f8e72fa145..96e99f754e 100644 --- a/Tests/Realm.Tests/Database/NotificationTests.cs +++ b/Tests/Realm.Tests/Database/NotificationTests.cs @@ -1670,10 +1670,10 @@ public void KeyPath_CanBeBuiltFromExpressions() public void KeyPath_WithInvalidExpressions_ThrowsException() { Assert.That(() => KeyPath.ForExpression(t => t.Equals(this)), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); + Throws.Exception.TypeOf().With.Message.Contains("The input expression is not a path to a property")); Assert.That(() => KeyPath.ForExpression(null!), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null (Parameter 'expression')")); + Throws.Exception.TypeOf().With.Message.Contains("The input expression cannot be null")); } [Test] @@ -1755,10 +1755,10 @@ void AssertKeyPathsCollectionCorrectness(KeyPathsCollection k, IEnumerable KeyPathsCollection.Of(t => t.ListSameType, null!), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression cannot be null (Parameter 'expression')")); + Throws.Exception.TypeOf().With.Message.Contains("The input expression cannot be null")); Assert.That(() => KeyPathsCollection.Of(t => t.Equals(this)), - Throws.Exception.TypeOf().With.Message.EqualTo("The input expression is not a path to a property")); + Throws.Exception.TypeOf().With.Message.Contains("The input expression is not a path to a property")); } [Test]