Migrating to using the new Embedded Object in MongoDb Realm

Hi!

I have an app that is local only for now, but I do want to get on MongoDbRealm sync.

In the old realm structure you would model a parent child relationship like this

public class Parent:Object {
let children = List
}

public class Child:Object {
private let parents:LinkingObjects = LinkingObjects(fromType: Parent.self , property: “children”)
func getParent()->Parent?{
return parent.first
}
}

But now I want the child objects to be embedded instead. How can I achieve this? Is there any example on how to migrate a non-embedded to embedded? Is there any recommended way to do this?

Right now I have simply switched the Child class to extend EmbeddedObject, but this would result in an exception on migration “Cannot convert object type 'Child' to embedded because objects have multiple incoming links.” How can I fix this?

Looks like I might have made this a lot harder for myself as these “child” objects are being linked to from multiple parents. This is probably where the error message is coming from.

I am guessing now that the easiest route might be to simply keep the old data in place and migrate data to new realm object using the new embedded document structure that will be better adapted to syncing.

I still think it would be nice to get some guidance here. Judging by this post https://github.com/realm/realm-java/pull/6730 I don’t think I am the only one wondering about migrating data to embedded objects.

After further examination… I am not sure it is possible to change the type of an object to embedded :confused: Might have to create another object instead and move the properties over.

For anyone lookin into this. Looks like it isn’t possible to migrate to embedded objects. The code doesn’t even reach the migration block and crashes before that. I really wish this was documented somewhere…

Hm… I think we need a bit more context here. When you’re trying to change the “embeddedness” of an object, are you doing that in the context of a local (non-synchronized) Realm? If so, you’ll need to handle that in the migration function - I’m not super familiar with the Swift migration API, but can ping someone on the Swift team to take a look or post an example snippet.

Alternatively, if you’re trying to change the schema of a synchronized Realm to make an object that was previously standalone embedded, that is not possible. This would be a destructive change which is disallowed when using sync. You’ll have to terminate and reinitialize Sync, at which point, migrating the on-device data is meaningless as it’ll be wiped when the Realm syncs with the server.

One final thing to note is that you can’t synchronize a local Realm - i.e. if you have on-device data in a local Realm, you can’t turn on sync for that one and you’ll have to manually copy data over to your synchronized Realm.

Clarifying which of these 3 scenarios is the one you’re trying to achieve will help us better understand your needs + point us to the docs that need improvement.

Hi! I am currently only using local realms and before migrating the app to use sync I want to migrate the local realms to use embedded objects to make the transition easier.

I have tried using a migration function for swift, but it crashes before even reaching the migration function.

But is it supposed to be possible to migrate old list data to be embedded for local realms?

It should be possible but may involve recreating the objects and re-adding them to the list. I’ll ping some folks on the Core/Cocoa teams and get back to you.

1 Like

For some more context. When I am changing my child object form Object to EmbeddedObject the app crashes with the message Cannot convert object type ‘MyObject’ to embedded because objects have multiple incoming links*.

This crash occurs before migration happens, so I am not sure how I would be able to recreate/re-add objects here.

That’s concerning - if the app crashes before reaching the migration, there are other issues to be addressed. Have you added a breakpoint in your code to see what’s actually crashing?

That’s a correct error as an Object and an EmbeddedObject are two different things. Additionally Embedded objects work “differently” than Objects and for example cannot have a PrimaryKey, which an Object should (generally) have. They cannot also not be shared - an Embedded object is embedded in a single parent Object.

We probably need to see some updated code as the code in the original question won’t work for EmbeddedObjects and cannot exist on its own.

Right - I spoke with some folks on the Cocoa team and this is indeed a bug on our end. We mark the table as embedded before the migration function runs, which obviously prevents you from executing a migration that would ensure that each object has only one parent.

While this is something that we plan to fix, the immediate workaround would be to create a different class - e.g. MyChildObject2 and in your migration function copy all MyChildObject data into MyChildObject2 and create all the proper relationships. If you don’t want to have messy/versioned class names in your project, you can keep the Swift name of the class and map it to a different database type. We don’t have docs how to do that but you can see it done in the Cocoa repo. Essentially this remaps the ugly __Role name to the friendlier PermissionRole.

1 Like

I’ll probably have to do some data changes anyway, so maybe the workaround isn’t bad. I mean, I would still have to convert all floats to doubles for example for sync. However, the code will definitely be messy if I change class names. As far as changing the mapping, it sounds a bit scary? My app is on both iOS and Android, so whatever change I do I have to do twice.

I am not in a super hurry to rush things out, so if this is something that will be fixed, then I guess the best thing would be to wait it out. Of course it is hard to ask when it will be fixed, but if it is a month or two then it is not a huge deal. But if it is more than that I’d have to go with a workaround.

Do you know if this is a high our low prio for the team?

If this isn’t an issue on Android, I could start the migration work on Android instead and wait for the fix on iOS/Swift

Unfortunately, I’m not sure when the fix will be in as I don’t work on the Cocoa SDK. I did file a Github issue you can follow though.

Can’t be too helpful for Android either - perhaps @ChristanMelchior can chime in and confirm whether Standalone → Embedded migrations are possible with the Java SDK?

1 Like

Thanks! I’ll follow the GitHub issue. And I could simply give it a try on Android. If the Android SDK has the same issue I’ll know about it quickly.

RealmObjectSchema.setEmbedded() is a function that can be used to convert between embedded and non-embedded data, but switching mode for the same model class is only supported for non-synced Realm as a synced Realm consider this a destructive migration which is not supported there.

Realm Java will ensure that the embeddedness constraints are upheld when you switch the mode (one parent, no primary key), so you should be able to marked the class as embedded using @RealmClass(embedded = true) and define an appropriate migration step using the RealmObjectSchema function.

@ChristanMelchior I am currently only using local realms, but I want to convert these to use EmbeddedObjects when appropriate and to use doubles instead of floats, so I can use the same models when switching to synced realms. I won’t make changes on synced realms (I don’t have them yet).

Thanks! I’ll give this a shot on Android then… and wait for the swift bug to be fixed :wink:

Just a quick update. I just gave this a try on Android/Java and it works as expected there, so the bug is iOS/Swift only.

I’ve just posted an update in Unable to execute a migration that changes the "embeddedness" of an object · Issue #7060 · realm/realm-swift · GitHub.

With Realm Cocoa 10.7.0 the relevant core changes have been released. This should work now.

2 Likes

Thanks! I’ll definitely check that out. I understood that the app would crash if there were orphaned children laying around somewhere? I would strongly prefer these to be deleted automatically rather than crashing, but is there any way to ensure this?

I’ll check out the new version regardless. This will make it a lot easier for me to start taking advantage of MongoDb Realm!

Glad to hear it makes your work easier!

Regarding the deletion: we talked about that and eventually decided we cannot automatically delete them since we would silently delete data that a user might still have needed but simply overlooked while making sure that every embedded object has exactly one backlink.
This option is the safe way.

So at the moment you would have to check manually that every object has one and only one backlink and delete objects that you do no longer need.

However, to make this even easier, I created Helper function for deleting orphaned embedded objects within migration · Issue #7145 · realm/realm-swift · GitHub which will offer a way to delete all orphaned objects within a migration.