Skip to content

Commit

Permalink
Add support for embedded objects (#2017)
Browse files Browse the repository at this point in the history
* Remove ISchemaSource

* Split the RealmObject hierarchy

* Pass embedded to native, start work on the weaver for embedded objects

* Wire more things

* Add list methods + recursive tests

* More tests

* Get more dynamic APIs working

* After rebase fixes

* Backlinks + more dynamic API

* oops

* Read embeddedness from disk

* add some preservation logic for xamarin.ios

* Fix dynamic methods for statically typed platforms

* Try to fix ios tests
  • Loading branch information
nirinchev committed Oct 2, 2020
1 parent 6297f0c commit 49bbc9b
Show file tree
Hide file tree
Showing 70 changed files with 2,944 additions and 1,060 deletions.
83 changes: 83 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,92 @@ v10 (TBD)
* We no longer support Realm Cloud (legacy), but instead the new "MongoDB Realm" Cloud. MongoDB Realm is a serverless platform that enables developers to quickly build applications without having to set up server infrastructure. MongoDB Realm is built on top of MongoDB Atlas, automatically integrating the connection to your database. ([#2011](https://github.com/realm/realm-dotnet/pull/2011))
* Remove support for Query-based sync, including the configuration parameters and the RLMSyncSubscription and SyncSubscription types. ([#2011](https://github.com/realm/realm-dotnet/pull/2011))
* Remove everything related to sync permissions, including both the path-based permission system and the object-level privileges for query-based sync. ([#2011](https://github.com/realm/realm-dotnet/pull/2011))
* Moved all API for dynamic access on the `Realm` class to `Realm.DynamicApi`:
* `Realm.CreateObject(string className, object primaryKey)` is now `Realm.DynamicApi.CreateObject(string className, object primaryKey)`.
* `Realm.All(string className)` is now `Realm.DynamicApi.All(string className)`.
* `Realm.RemoveAll(string className)` is now `Realm.DynamicApi.RemoveAll(string className)`.
* `Realm.Find(string className, long? primaryKey)` is now `Realm.DynamicApi.Find(string className, long? primaryKey)`.
* `Realm.Find(string className, string primaryKey)` is now `Realm.DynamicApi.Find(string className, string primaryKey)`.

### Enhancements
* Add support for the Decimal128 data type. This is a 128-bit IEEE 754 decimal floating point number. Properties of this type can be declared either as `MongoDB.Bson.Decimal128` type or the built-in `decimal` type. Note that .NET's built-in decimal is 96-bit, so it cannot represent the full range of numbers, representable by `Decimal128`. (PR [#2014](https://github.com/realm/realm-dotnet/pull/2014))
* Add support for embedded objects. Embedded objects are objects which are owned by a single parent object, and are deleted when that parent object is deleted or their parent no longer references them. Embedded objects are declared by subclassing `EmbeddedObject` instead of `RealmObject`. Reassigning an embedded object is not allowed and neither is linking to it from multiple parents. Querying for embedded objects directly is also disallowed as they should be viewed as complex structures belonging to their parents as opposed to standalone objects. A trivial example is:

```csharp
public class Address : EmbeddedObject
{
public string Street { get; set; }

public string City { get; set; }
}

public class Person : RealmObject
{
public string Name { get; set; }

// Address is an embedded object - you reference it as usual
public Address Address { get; set; }
}

public class Company : RealmObject
{
public string PhoneNumber { get; set; }

// Embedded objects can be contained in lists too
public IList<Address> OfficeAddresses { get; }
}
```

* Added new dynamic methods for instantiating embedded objects:
* `Realm.DynamicApi.CreateEmbeddedObjectForProperty` should be used to create an embedded object and assign it to a parent's property. For example:

```csharp
// static API
var person = new Person();
person.Address = new Address
{
City = "New York"
};

// dynamic API
var dynamicPerson = realm.DynamicApi.CreateObject("Person");
var address = realm.DynamicApi.CreateEmbeddedObjectForProperty(dynamicPerson, "Address")
address.City = "New York";
```

* `Realm.DynamicApi.AddEmbeddedObjectToList` should be used to create an embedded object and add it to a parent's list property.
* `Realm.DynamicApi.InsertEmbeddedObjectInList` should be used to create an embedded object and insert it in a parent's list property at a specified index.
* `Realm.DynamicApi.SetEmbeddedObjectInList` should be used to create an embedded object and set it at an index in a parent's list property.

```csharp
// static API
var company = new Company();
company.OfficeAddresses.Add(new Address
{
City = "New York"
});

company.OfficeAddresses.Insert(0, new Address
{
City = "Palo Alto"
});

company.OfficeAddresses[1] = new Address
{
City = "New Jersey"
};

// dynamic API
var dynamicCompany = realm.DynamicApi.CreateObject("Company");
var officeToAdd = realm.DynamicApi.AddEmbeddedObjectToList(dynamicCompany.OfficeAddresses) ;
officeToAdd.City = "New York";

var officeToInsert = realm.DynamicApi.InsertEmbeddedObjectInList(dynamicCompany. OfficeAddresses, 0);
officeToInsert.City = "Palo Alto";

var officeToSet = realm.DynamicApi.SetEmbeddedObjectInList(dynamicCompany. OfficeAddresses, 1);
officeToSet.City = "New Jersey";
```

### Fixed

Expand Down
24 changes: 17 additions & 7 deletions Realm/Realm.Fody/Extensions/PropertyDefinitionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
using System.Text.RegularExpressions;
using Mono.Cecil;
using Mono.Cecil.Cil;

using RealmWeaver;
using static ModuleWeaver;

[EditorBrowsable(EditorBrowsableState.Never)]
Expand Down Expand Up @@ -123,21 +123,21 @@ internal static FieldReference GetBackingField(this PropertyDefinition property)
.SingleOrDefault();
}

internal static bool IsPrimaryKey(this PropertyDefinition property)
internal static bool IsPrimaryKey(this PropertyDefinition property, ImportedReferences references)
{
Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Primary key properties only make sense on RealmObject classes");
Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Primary key properties only make sense on RealmObject/EmbeddedObject classes");
return property.CustomAttributes.Any(a => a.AttributeType.Name == "PrimaryKeyAttribute");
}

internal static bool IsRequired(this PropertyDefinition property)
internal static bool IsRequired(this PropertyDefinition property, ImportedReferences references)
{
Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Required properties only make sense on RealmObject classes");
Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Required properties only make sense on RealmObject/EmbeddedObject classes");
return property.CustomAttributes.Any(a => a.AttributeType.Name == "RequiredAttribute");
}

internal static bool IsIndexable(this PropertyDefinition property)
internal static bool IsIndexable(this PropertyDefinition property, ImportedReferences references)
{
Debug.Assert(property.DeclaringType.BaseType.Name == "RealmObject", "Required properties only make sense on RealmObject classes");
Debug.Assert(property.DeclaringType.IsValidRealmObjectBaseInheritor(references), "Required properties only make sense on RealmObject/EmbeddedObject classes");
var propertyType = property.PropertyType;
if (propertyType.IsRealmInteger(out var isNullable, out var backingType))
{
Expand All @@ -152,6 +152,16 @@ internal static bool IsIndexable(this PropertyDefinition property)
return _indexableTypes.Contains(propertyType.FullName);
}

public static bool IsEmbeddedObjectInheritor(this TypeDefinition type, ImportedReferences references) => type.BaseType.IsSameAs(references.EmbeddedObject);

public static bool IsRealmObjectInheritor(this TypeDefinition type, ImportedReferences references) => type.BaseType.IsSameAs(references.RealmObject);

public static bool IsValidRealmObjectBaseInheritor(this TypeDefinition type, ImportedReferences references) => type.IsEmbeddedObjectInheritor(references) || type.IsRealmObjectInheritor(references);

public static bool ContainsRealmObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsRealmObjectInheritor(references);

public static bool ContainsEmbeddedObject(this PropertyDefinition property, ImportedReferences references) => property.PropertyType.Resolve().IsEmbeddedObjectInheritor(references);

public static bool IsRealmInteger(this TypeReference type, out bool isNullable, out TypeReference genericArgumentType)
{
var nullableMatch = NullableRegex.Match(type.FullName);
Expand Down
32 changes: 19 additions & 13 deletions Realm/Realm.Fody/ImportedReferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ internal abstract class ImportedReferences

public TypeReference RealmObject { get; private set; }

public TypeReference RealmObjectBase { get; private set; }

public TypeReference EmbeddedObject { get; private set; }

public TypeReference RealmIntegerOfT { get; private set; }

public MethodReference RealmIntegerOfT_ConvertToT { get; private set; }
Expand Down Expand Up @@ -235,7 +239,9 @@ private void InitializeFrameworkMethods()
private void InitializeRealm(IMetadataScope realmAssembly)
{
Realm = new TypeReference("Realms", "Realm", Module, realmAssembly);
RealmObjectBase = new TypeReference("Realms", "RealmObjectBase", Module, realmAssembly);
RealmObject = new TypeReference("Realms", "RealmObject", Module, realmAssembly);
EmbeddedObject = new TypeReference("Realms", "EmbeddedObject", Module, realmAssembly);
RealmSchema_PropertyType = new TypeReference("Realms.Schema", "PropertyType", Module, realmAssembly, valueType: true);

{
Expand Down Expand Up @@ -269,48 +275,48 @@ private void InitializeRealm(IMetadataScope realmAssembly)
Realm_Add.Parameters.Add(new ParameterDefinition(Types.BooleanReference));
}

RealmObject_get_IsManaged = new MethodReference("get_IsManaged", Types.BooleanReference, RealmObject) { HasThis = true };
RealmObject_get_Realm = new MethodReference("get_Realm", Realm, RealmObject) { HasThis = true };
RealmObject_RaisePropertyChanged = new MethodReference("RaisePropertyChanged", Types.VoidReference, RealmObject)
RealmObject_get_IsManaged = new MethodReference("get_IsManaged", Types.BooleanReference, RealmObjectBase) { HasThis = true };
RealmObject_get_Realm = new MethodReference("get_Realm", Realm, RealmObjectBase) { HasThis = true };
RealmObject_RaisePropertyChanged = new MethodReference("RaisePropertyChanged", Types.VoidReference, RealmObjectBase)
{
HasThis = true,
Parameters = { new ParameterDefinition(Types.StringReference) }
};

{
RealmObject_GetObjectValue = new MethodReference("GetObjectValue", Types.VoidReference, RealmObject) { HasThis = true };
var T = new GenericParameter(RealmObject_GetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObject) } };
RealmObject_GetObjectValue = new MethodReference("GetObjectValue", Types.VoidReference, RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_GetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } };
RealmObject_GetObjectValue.ReturnType = T;
RealmObject_GetObjectValue.GenericParameters.Add(T);
RealmObject_GetObjectValue.Parameters.Add(new ParameterDefinition(Types.StringReference));
}

{
RealmObject_SetObjectValue = new MethodReference("SetObjectValue", Types.VoidReference, RealmObject) { HasThis = true };
var T = new GenericParameter(RealmObject_SetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObject) } };
RealmObject_SetObjectValue = new MethodReference("SetObjectValue", Types.VoidReference, RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_SetObjectValue) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } };
RealmObject_SetObjectValue.GenericParameters.Add(T);
RealmObject_SetObjectValue.Parameters.Add(new ParameterDefinition(Types.StringReference));
RealmObject_SetObjectValue.Parameters.Add(new ParameterDefinition(T));
}

{
RealmObject_GetListValue = new MethodReference("GetListValue", new GenericInstanceType(IListOfT), RealmObject) { HasThis = true };
RealmObject_GetListValue = new MethodReference("GetListValue", new GenericInstanceType(IListOfT), RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_GetListValue);
(RealmObject_GetListValue.ReturnType as GenericInstanceType).GenericArguments.Add(T);
RealmObject_GetListValue.GenericParameters.Add(T);
RealmObject_GetListValue.Parameters.Add(new ParameterDefinition(Types.StringReference));
}

{
RealmObject_GetBacklinks = new MethodReference("GetBacklinks", new GenericInstanceType(IQueryableOfT), RealmObject) { HasThis = true };
var T = new GenericParameter(RealmObject_GetBacklinks) { Constraints = { new GenericParameterConstraint(RealmObject) } };
RealmObject_GetBacklinks = new MethodReference("GetBacklinks", new GenericInstanceType(IQueryableOfT), RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_GetBacklinks) { Constraints = { new GenericParameterConstraint(RealmObjectBase) } };
(RealmObject_GetBacklinks.ReturnType as GenericInstanceType).GenericArguments.Add(T);
RealmObject_GetBacklinks.GenericParameters.Add(T);
RealmObject_GetBacklinks.Parameters.Add(new ParameterDefinition(Types.StringReference));
}

{
RealmObject_GetPrimitiveValue = new MethodReference("GetPrimitiveValue", Types.VoidReference, RealmObject) { HasThis = true };
RealmObject_GetPrimitiveValue = new MethodReference("GetPrimitiveValue", Types.VoidReference, RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_GetPrimitiveValue);
RealmObject_GetPrimitiveValue.ReturnType = T;
RealmObject_GetPrimitiveValue.GenericParameters.Add(T);
Expand All @@ -319,7 +325,7 @@ private void InitializeRealm(IMetadataScope realmAssembly)
}

{
RealmObject_SetPrimitiveValue = new MethodReference("SetPrimitiveValue", Types.VoidReference, RealmObject) { HasThis = true };
RealmObject_SetPrimitiveValue = new MethodReference("SetPrimitiveValue", Types.VoidReference, RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_SetPrimitiveValue);
RealmObject_SetPrimitiveValue.GenericParameters.Add(T);
RealmObject_SetPrimitiveValue.Parameters.Add(new ParameterDefinition(Types.StringReference));
Expand All @@ -328,7 +334,7 @@ private void InitializeRealm(IMetadataScope realmAssembly)
}

{
RealmObject_SetPrimitiveValueUnique = new MethodReference("SetPrimitiveValueUnique", Types.VoidReference, RealmObject) { HasThis = true };
RealmObject_SetPrimitiveValueUnique = new MethodReference("SetPrimitiveValueUnique", Types.VoidReference, RealmObjectBase) { HasThis = true };
var T = new GenericParameter(RealmObject_SetPrimitiveValueUnique);
RealmObject_SetPrimitiveValueUnique.GenericParameters.Add(T);
RealmObject_SetPrimitiveValueUnique.Parameters.Add(new ParameterDefinition(Types.StringReference));
Expand Down
Loading

0 comments on commit 49bbc9b

Please sign in to comment.