EventGet 50% off your ticket to MongoDB.local NYC on May 2. Use code Web50!Learn more >>
MongoDB Developer
Realm
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
Realmchevron-right

Advanced Data Modeling with Realm .NET

Nikola Irinchev7 min read • Published Feb 01, 2022 • Updated Oct 19, 2022
RealmC#
Facebook Icontwitter iconlinkedin icon
MongoDB Realm Logo
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Realm's intuitive data model approach means that in most cases, you don't even think of Realm models as entities. You just declare your POCOs, have them inherit from RealmObject, and you're done. Now you have persistable models, with INotifyPropertyChanged capabilities all wired up, that are also "live"—i.e., every time you access a property, you get the latest state and not some snapshot from who knows how long ago. This is great and most of our users absolutely love the simplicity. Still, there are some use cases where being aware that you're working with a database can really bring your data models to the next level. In this blog post, we'll evaluate three techniques you can apply to make your models fit your needs even better.
Build better mobile apps with Atlas Device Sync: Atlas Device Sync is a fully-managed mobile backend-as-a-service. Leverage out-of-the-box infrastructure, data synchronization capabilities, built-in network handling, and much more to quickly launch enterprise-grade mobile apps. Get started now by build: Deploy Sample for Free!

Constructor Validation

One of the core requirements of Realm is that all models need to have a parameterless constructor. This is needed because Realm needs to be able to instantiate an object without figuring out what arguments to pass to the constructor. What not many people know is that you can make this parameterless constructor private to communicate expectations to your callers. This means that if you have a Person class where you absolutely expect that a Name is provided upon object creation, you can have a public constructor with a name argument and a private parameterless one for use by Realm:
And I know what some of you may be thinking: "Oh no! 😱 Does that mean Realm uses the suuuuper slow reflection to create object instances?" Fortunately, the answer is no. Instead, at compile time, Realm injects a nested helper class in each model that has a CreateInstance method. Since the helper class is nested in the model classes, it has access to private members and is thus able to invoke the private constructor.

Property Access Modifiers

Similar to the point above, another relatively unknown feature of Realm is that persisted properties don't need to be public. You can either have the entire property be private or just one of the accessors. This synergizes nicely with the private constructor technique that we mentioned above. If you expose a constructor that explicitly validates the person's name, it would be fairly annoying to do all that work and have some code accidentally set the property to null the very next line. So it would make sense to make the setter of the name property above private:
That way, you're communicating clearly to the class consumers that they need to provide the name at object creation time and that it can't be changed later. A very common use case here is to make the Id setter private and generate a random Id at object creation time:
Sometimes, it makes sense to make the entire property private—typically, when you want to expose a different public property that wraps it. If we go back to our Person and Name example, perhaps we want to allow changing the name, but we want to still validate the new name before we persist it. Then, we create a private autoimplemented property that Realm will use for persistence, and a public one that does the validation:
This is quite neat as it makes the public API of your model safe, while preserving its persistability. Of note is the MapTo attribute applied to _Name. It is not strictly necessary. I just added it to avoid having ugly column names in the database. You can use it or not use it. It's totally up to you. One thing to note when utilizing this technique is that Realm is completely unaware of the relationship between Name and _Name. This has two implications. 1) Notifications will be emitted for _Name only, and 2) You can't use LINQ queries to filter Person objects by name. Let's see how we can mitigate both:
For notifications, we can override OnPropertyChanged and raise a notification for Name whenever _Name changes:
The code is fairly straightforward. OnPropertyChanged will be invoked whenever any property on the object changes and we just re-raise it for the related Name property. Note that, as an optimization, OnPropertyChanged will only be invoked if there are subscribers to the PropertyChanged event. So if you're testing this out and don't see the code get executed, make sure you added a subscriber first.
The situation with queries is slightly harder to work around. The main issue is that because the property is private, you can't use it in a LINQ query—e.g., realm.All<Person>().Where(p => p._Name == "Peter") will result in a compile-time error. On the other hand, because Realm doesn't know that Name is tied to _Name, you can't use p.Name == "Peter" either. You can still use the string-based queries, though. Just remember to use the name that Realm knows about—i.e., the string argument of MapTo if you remapped the property name or the internal property (_Name) if you didn't:

Using Unpersistable Data Types

Realm has a wide variety of supported data types—most primitive types in the Base Class Library (BCL), as well as advanced collections, such as sets and dictionaries. But sometimes, you'll come across a data type that Realm can't store yet, the most obvious example being enums. In such cases, you can build on top of the previous technique to expose enum properties in your models and have them be persisted as one of the supported data types:
Using this technique, you can persist many other types, as long as they can be converted losslessly to a persistable primitive type. In this case, we chose string, but we could have just as easily used integer. The string representation takes a bit more memory but is also more explicit and less error prone—e.g., if you rearrange the enum members, the data will still be consistent.
All that is pretty cool, but we can take it up a notch. By building on top of this idea, we can also devise a strategy for representing complex data types, such as Vector3 in a Unity game or a GeoCoordinate in a location-aware app. To do so, we'll take advantage of embedded objects—a Realm concept that represents a complex data structure that is owned entirely by its parent. Embedded objects are a great fit for this use case because we want to have a strict 1:1 relationship and we want to make sure that deleting the parent also cleans up the embedded objects it owns. Let's see this in action:
In this example, we've defined a Vector3Model that roughly mirrors Unity's Vector3. It has three float properties representing the three components of the vector. We've also utilized what we learned in the previous sections. It has a private constructor to force consumers to always construct it with a Vector3 argument. We've also marked its properties as private as we don't want consumers directly interacting with them. We want users to always call ToVector3 to obtain the Unity type. And for our Powerup model, we're doing exactly that in the publicly exposed Position property. Note that similarly to our Person example, we're making sure to raise a notification for Position whenever _Position changes.
And similarly to the exaple in the previous section, this approach makes querying via LINQ impossible and we have to fall back to the string query syntax if we want to find all powerups in a particular area:

Conclusion

The list of techniques above is by no means meant to be exhaustive. Neither is it meant to imply that this is the only, or even "the right," way to use Realm. For most apps, simple POCOs with a list of properties is perfectly sufficient. But if you need to add extra validations or persist complex data types that you're using a lot, but Realm doesn't support natively, we hope that these examples will give you ideas for how to do that. And if you do come up with an ingenious way to use Realm, we definitely want to hear about it. Who knows? Perhaps we can feature it in our "Advanced^2 Data Modeling" article!

Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

Build an Infinite Runner Game with Unity and the Realm Unity SDK


Feb 03, 2023 | 12 min read
News & Announcements

New Realm Data Types: Dictionaries/Maps, Sets, Mixed, and UUIDs


Oct 19, 2022 | 18 min read
Tutorial

How to Write End-to-End Tests for MongoDB Realm Serverless Apps


Sep 23, 2022 | 7 min read
Tutorial

Continuously Building and Hosting our Swift DocC Documentation using Github Actions and Netlify


May 10, 2022 | 6 min read
Table of Contents