Freigeben über


Using Realm Mobile Database in a converted desktop app with the Desktop Bridge

Realm Mobile Database is an open source project that has quickly become very popular, especially among mobile developers. It’s an alternative to SQLite and it’s a way to store complex data in a permanent way, pretty much like you would do with a regular database. Some of the advantages of this technology are that:

  1. Like SQLite, it doesn’t need a “man in the middle” service (DBMS) to talk with the database (like SQL Server, Oracle, MySQL, etc.), but the application has direct access to it, making it a perfect match for mobile and simple applications.
  2. It’s based on an object oriented model so, similarly in a way you do when you work with technologies like Entity Framework, you don’t have to write manual SQL queries to perform operations but you can keep working with classes, objects and powerful manipulation languages like LINQ.
  3. It’s cross-platform, so you can share your database with the vast majority of platforms on the market.
  4. It supports UI notifications out of the box, so the user interface of your application can be automatically updated every time the data in the database changes.

Why I’m talking about this technology in the AppConsult blog? Because the developer team behind Realm has recently announced support for .NET and Portable Class Libraries, which means that now you can use a Realm database also in Xamarin and Windows desktop applications. Can you see the direction where my post is going? As you’ll probably know if you have followed all the posts so far about the Desktop Bridge, there are some requirements that a converted desktop app must respect since:

  1. A converted app needs to always run in user mode, you can’t require the user to have administrative rights in order to use the app or parts of it.
  2. The deployment of a converted app is a “one click” procedure: you press an Install button (like when you use the Store), you wait for the package to be downloaded and installed and then you launch it.

As such, some scenarios that would require a more complex configuration environment (like installing a SQL Server instance to handle a database) aren’t supported. When we’re talking about the Desktop Bridge, technologies like SQLite or Realm Mobile Database are just perfect: the database is stored in a single file and it’s directly accessed by the application, so we can simply deploy it in any folder of the computer and let the application read and write from it, without having to install any particular service.

There’s a lot of documentation on the official website that covers how to use Realm in a .NET application, including all the advanced (like encryption and synchronization) and corner case scenarios. In this post I would like to focus on the basic usage of the Realm Mobile Database and how you can leverage it in a desktop app converted using the Desktop Bridge, since there are some important caveats to consider.

For the purpose of this post, we’re going to create a simple WPF app that will allow us to:

  1. Create a new database to store a list of customers.
  2. Add some customers to the database.
  3. Retrieve the list of customers stored in the database.
  4. Update the info about one of the customers stored in the database.

Let’s start!

Configure Realm Mobile Database for the Desktop Bridge

Let’s start by opening Visual Studio and by creating a new WPF project, which you can find by following the path File –> New project –> Templates –> Visual C# –> Windows Classic Desktop –> WPF App (.NET Framework) . Make sure to choose at least .NET 4.6.1, since it’s the minimum version supported by the Desktop Bridge. Adding support to Realm Mobile Database is easy, since the team has released it as a NuGet package: right click on the WPF project, choose Manage NuGet Packages and look for and install the package identified by the Realm name.

realm1

By default, using Realm is quite easy: you can use the Realm class (included in the Realms namespace) and call the static method GetInstance() to get a reference to the database, which you will need later to perform all the standard CRUD operations (adding / removing / updating / reading an item). As such, the initialization code could be as easy as the following one:

 private void InitializeRealm()
{
   var realm = Realm.GetInstance();
}

However, when it comes to an app converted with the Desktop Bridge, this approach may have a downside. If you’ll try to execute it, you will notice that, by default, the database will be created in the Documents folder of the user. You will find, in fact, three new elements in this library:

  1. The main database, which is a file called default.realm.
  2. A lock file, used to handle concurrent operations, which is a file called default.realm.lock.
  3. A folder used to manage the database, called default.realm.management.

This scenario is absolutely supported also by a converted desktop app: since it’s executed with the runFullTrust permission, it has the chance to read and write folders in the Documents folder of the user without problems (since it’s a folder which doesn’t require admin rights to be accessed). So, why I’m saying that using the default approach can be a downside? One of the benefits of the Desktop Bridge compared to a traditional desktop app is that it offers a cleaner deployment experience, in terms of install / uninstall / upgrade operations. As such, storing the database of the application in the Documents library isn’t a great idea, because it’s the kind of user generated content that doesn’t make sense without the application that has created it: a regular user won’t know what to do with a default.realm file after he has uninstalled your application. Wouldn’t be better to leverage the AppData folder provided by Windows for this scenario? Additionally, thanks to the file system redirection provided by the Desktop Bridge, storing the database in the AppData folder would mean that, when the user will decide to uninstall your app, also the database will be removed, without leaving unnecessary files on the user’s disk.

We can achieve this goal thanks to a class called RealmConfiguration, that we can pass as parameter of the GetInstance() method and that we can use to customize the configuration of the database. One of the configurable parameters is exactly the path of the database. Here is a complete sample initialization code:

 private void InitializeRealm()
{
    string path = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\RealmDesktop";
    bool isDatabaseInitialized = Directory.Exists(path);
    if (!isDatabaseInitialized)
    {
        Directory.CreateDirectory(path);
    }

    string file = $"{path}\\default.realm";

    RealmConfiguration config = new RealmConfiguration(file);
    realm = Realm.GetInstance(config);
}

We’re using the standard .NET APIs to get access to the local AppData folder of the user (thanks to the enumerator Environment.SpecialFolder.LocalApplicationData, which represents the %LOCALAPPDATA% system variable). Inside this special folder, we create sub folder with the name of our application (in this case, RealmDesktop), where we’re going to store the database. The first part of the code is pretty straightforward: if the app is running for the first time, the RealmDesktop folder doesn’t exist yet, so we need to create it first. Then we combine the path of the folder with the name of the database (in our case, we keep the standard name default.realm) and we pass it as initialization parameter of a new RealmConfiguration object. Then we can leverage the same GetInstance() method offered by the Realm class as before, but passing the configuration as parameter.

If we want to see the outcome of this initialization, simply invoke the InitializeRealm() method in the constructor of the MainWindow.xaml.cs file, like in the following example:

 public MainWindow()
{
    InitializeComponent();
    InitializeRealm();
}

If the app is running as a native desktop app, you will find the database created in the following path:

C:\Users\<username>\AppData\Local\RealmDesktop\

Since we’re leveraging the AppData folder, when the app will run as converted through the Desktop Bridge, Windows will take care for us of performing the proper file system redirection. To see it in action, just follow the guidance of one the previous posts on this blog: add a new WinJS project to the solution and make sure to edit the .csproj file of the WPF project to add an AfterBuild task that will take care of copying the output of the WPF build inside a subfolder of the WinJS project, which we’ll use to create our packages, launch it as a converted app, etc. I won’t explain the details of all the steps you need to follow, since they are detailed in the previous blog post.

At the end, this is how your solution should look like:

realm2

And here comes the second caveat to keep in mind when it comes to implement Realm in a converted desktop app. If you explore the bin/Debug folder of the standard desktop application, you will see that it has the following structure:

realm3

As you can see, other than the standard executable and DLL libraries that have been copied also inside our WinJS project, we have alsoa lib folder, which contains a file called realm-wrappers.dll. However, you will find two versions of it: one compiled for x86 and one for x64. The reason is that, under the hood, the Realm Mobile Database is implemented using C++ and, as such, the core library can’t be cross-compiled for both architectures, but you need to have a specific version based on the architecture for which the converted version of the app is compiled. As such, let’s say that we want to create a converted version compiled for the x86 architecture: we’ll need to enter into the lib/x86 folder, copy the realm-wrappers.dll file and paste it inside the win32 folder of our WinJS project. This requirement, instead, doesn’t apply when the app is launched as native: the reason is that the Realm NuGet package installs a component that takes care of copying the proper version of the DLL, based on the architecture for which the native desktop app is being compiled. In case of a converted app, this component doesn’t work, so we need to manually take care of copying the right DLL. This is what we did in the previous example: we took the real-wrappers.dll file inside the lib/x86 folder and we copied it in the win32 folder of the WinJS project. Eventually, if you have the requirement to produce different packages for different architectures, you can leverage a post-build task: one for the configuration when the native desktop app is compiled for the x86 architecture, which copies the 32 bit version of the library, and one for the configuration when the app is compiled for the x64 architecture, which copies the 64 bit version of the library.

Now try to launch the converted version of the app, by setting the WinJS project as startup, as configuration the x86 architecture and by choosing, from the Visual Studio menu, Debug –> Start without debugging (remember that the WinJS project facilitates our job in launching the converted version, creating the app packages, etc., but it can’t be used for real debugging). This time, since the app will run as converted, you’ll be able to see the Desktop Bridge file system redirection at work: the new database file will be placed in the local folder of the app, under the c:\Users\<username>\AppData\Local\Packages folder. In my case, for example, I was able to find the default.realm file in the path

C:\Users\<username>\AppData\Local\Packages\MatteoPagani.RealmDesktop_e8f4dqfvn1be6\LocalCache\Local\RealmDesktop

From a Desktop Bridge point of view, that’s all you need to know to understand how to leverage Realm Mobile Database in a converted desktop app:

  1. If you want to keep the install / uninstall experience clean and efficient, remember to change the default path where the database is created, so that it can be deleted when the app is uninstalled (unless, of course, the ability of keeping the database across multiple installs / uninstall is one of your requirements).
  2. Remember to manually copy the realm-wrappers.dll file from the lib/x86 or lib/x64 folder, which is placed inside the bin folder of the desktop project, inside the win32 folder of the WinJS folder, based on the architecture for which you want to compile and distribute your application. Of course, this choice affects also the procedure of creating the AppX using the option Store –> Create app packages that you can find when you right click on the WinJS project. If, for example, you have chosen to use the x86 version of the realm-wrappers.dll library, you will have to create an AppX which contains just the x86 version of the app.

Working with Realm

Again, the purpose of this post is to share with you how to properly use Realm inside a desktop app converted with the Desktop Bridge, so I won’t go into all the details and opportunities that you can leverage working with the Realm database. However, at the same time, I don’t want you to “tease you” to learn how to configure Realm, without showing at least how to leverage it to perform some basic operations.

As such, let’s start by defining a simple user interface in the MainWindow.xaml page, that will allow us to handle a collection of customers:

 <Window x:Class="RealmDesktop.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RealmDesktop"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="250">
    <Grid>
        <StackPanel HorizontalAlignment="Center" Margin="12, 0 ,0 ,0">
            <TextBlock Text="Name" />
            <TextBox x:Name="txtName" Width="200" />
            <TextBlock Text="Surname"  />
            <TextBox x:Name="txtSurname" Width="200"  />
            <Button Content="Save user" Click="OnSaveUser" Width="150" Margin="0, 20, 0, 0" />
            <Button Content="List customers" Click="OnListCustomers" Width="150" />
            
            <ListView x:Name="listCustomers" Margin="0, 20, 0,0 ">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Path=Name}" />
                            <TextBlock Text="{Binding Path=Surname}" Margin="3, 0, 0, 0" />
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

            <Button Content="Update customer" Click="OnUpdateCustomer" Width="150" Margin="0, 20, 0, 0" />
        </StackPanel>
    </Grid>
</Window>

As you can see, the user interface is very simple:

  1. A couple of TextBox control, to allow the user to specify the name and the surname of the customer .
  2. A button to add the customer to the database.
  3. A button to retrieve the list of existing customers, so that we can display them in the ListView control placed at the bottom.
  4. A button to update one of the fields of one of the existing customers.

This is how the interface looks like:

realm4

Now let’s see how to implement each feature. The first thing we need, as for every other scenario where we deal with data, is a model that represents the entities we need to work with. In this case, we’re working with customers, so we’re going to create a class called Person to store his name and surname:

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

You can immediately notice a peculiar feature, which is requested in order for Realm to map a table for this class: it has to inherit from the RealmObject class. There’s also another advantage in using it as a base class for our entity, but we’ll see it later.

Now that we have an entity, we can start adding instances of it inside the Realm database. To achieve this goal, the Realm instance we have retrieved at startup (thanks to the InitializeRealm() method) offers a method called Write() , which can be used to start a transaction: either the operation will succeed and the data will be successfully saved or the operation will fail and the database will be rollback prior to the change. Inside the Write() statement, we can define with an anonymous method the operation we want to perform. In this case, we want to add a new instance of a Person object, created by leveraging the data inserted by the user in the two TextBox controls:

 private void OnSaveUser(object sender, RoutedEventArgs e)
{
    realm.Write(() =>
    {
        Person person = new Person
        {
            Name = txtName.Text,
            Surname = txtSurname.Text
        };

        realm.Add(person);
    });

    txtName.Text = string.Empty;
    txtSurname.Text = string.Empty;
}

To achieve this goal, it’s enough  to call the Add() method on the Realm object, by passing as parameter the Person object we have just created. Now try to repeat the same operation and to add more customers, so that we have more data to work with. Now let’s try a basic reading operation: let’s retrieve all the elements and count how many items we have added.

 private void OnGetCount(object sender, RoutedEventArgs e)
{
    var list = realm.All<Person>();
    MessageBox.Show($"Number of customers: {list.Count()}");
}

To access to the items stored in a table we can use the All<T>() method, passing as T the type of object we want to retrieve (in this case, Person). One of the benefits of Realm (which helps to achieve good writing / reading performances) is that when when we call the All<T>() method we have just retrieved a reference to the list, but we haven’t actually executed any query. The type of the result returned by the All<T>() method, in fact, is IQueryable<T>. The real operation on the database is performed only when we move on and we define the kind of query we want to perform: in this sample, the query is effectively executed when we call the Count() method, so that we can display to the user the number of customers inserted using a MessageBox.

We can use the same approach to display the list of customers using the ListView control we have placed in the XAML:

 private void OnListCustomers(object sender, RoutedEventArgs e)
{
    var result = realm.All<Person>().ToList();
    listCustomers.ItemsSource = result;
}

The only difference is that, in this case, instead of the Count() method, we use the ToList() one, to perform the query and get the list of customers stored in the table. If you have some experience with XAML / C# development and binding, the rest of the code shouldn’t be complex to understand: we have set the list of customers as ItemsSource of the ListView control. Then, since in the ListView control we have defined an ItemTemplate to display the Name and Surname property of each Person object, the final result will be that the list in the XAML will be populated with list of customers we have previously added.

Last but not the least, we can use the IQueryable<T> result also to perform more complex queries:

 private void OnUpdateCustomer(object sender, RoutedEventArgs e)
{
    Person person = realm.All<Person>().FirstOrDefault(x => x.Surname == "Pagani");
    realm.Write(() =>
    {
        person.Name = "Giulia";
    });
}

As with Entity Framework or LINQ to SQL, all the queries are performed using LINQ statements: in this case, we’re using the FirstOrDefault() statement to retrieve the first customer of the collection whichsurname is equal to Pagani. The sample code shows also two additional Realm features:

  1. The Write() method can be used to perform any kind of operation with the database, not just adding, but also deleting or updating. In this case, we’re changing thevalue of the Name property of the customer we have just retrieved and storing the update in the database.
  2. After pressing the button, you would have noticed that, if you have previously inserted a customer with surname Pagani, his name would have been immediately changed in the user interface with the new one, Giulia. Do you remember that I told you that there was another advantage in using the RealmObject class as a base one for the Person entity? Well, you have just found it: out of the box, the RealmObject class will make your object’s properties implement the INotifyPropertyChanged interface, which is the key feature to dispatch changes in the objects defined in code to the controls that are in binding with them. This is the reason why, without any extra effort from you, the ListView will automatically update his visual layout to display the new name of the customer: every time we change the property of one of the items in our list, the object will send a notification to the UI, so the control will automatically update itself.

Wrapping up

In this post we have learned two new concepts at the same time: the first one is a new emerging technology, called Realm Database, which is becoming widely adopted by developers who has the requirement of store complex data in a permanent way in their applications. This technology was already available for many mobile platforms, like iOS or Android, but since a few weeks it supports also the .NET world, allowing us to leverage it also in Windows applications or in cross-platform applications developed with Xamarin. The second concept we have learned is how to leverage this new technology inside a desktop application converted with the Desktop Bridge, so that we can keep a clean deployment process and eventually publish it on the Store.

You’ll find the sample code of the project used for this post on my GitHub repository at the URL https://github.com/qmatteoq/DesktopBridge/tree/master/Extras/RealmDesktop

In this project the Realm NuGet package has been added directly in the WPF application, but remember that also Portable Class Libraries are supported: this means that your data layer (so the definition of the entities and the classes that performs the various operations on the database) can be included in a separate library and shared with a mobile app for Android or iOS developed with Xamarin. Additionally, the Realm team has confirmed that also support for the Universal Windows Platform is coming soon, so you’ll be able to leverage this new technology not only with traditional desktop or converted app, but also with new Windows 10 apps.

Happy coding!