Using Immutable Objects with SQLite-Net

SQLite-NET has become the most popular database, especially amongst Xamarin developers, but it hasn't supported Immutable Objects, until now! Thanks to Init-Only Properties in C#9.0, we can now use Immutable Objects with our SQLite database!

Using Immutable Objects with SQLite-Net

SQLite-NET has become the most popular database, especially amongst Xamarin developers, but it hasn't supported Immutable Objects, until now!

If you're already familiar with Immutable vs. Mutable objects, feel free to scroll straight to Immutable Objects + SQLite-net.

What is an Immutable Object?

An Immutable Object is a object whose values are defined at initialization and cannot be changed. Let's take a look at the example, below.

Immutable Object

public class Person
{
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    
    public string FirstName { get; } //Read-only Property, cannot be changed after initialization
    public string LastName { get; } //Read-only Property, cannot be changed after initialization
}

This is an immutable object whose values are required at initialization and can never be changed.

var brandon = new Person("Brandon", "Minnick");

// The values of `brandon` can never be changed 
// I.e. `person.FirstName = "Kim"` generates a compiler error

Why Immutable Objects?

Now, you're probably wondering "Who cares about immutability?"

Immutability is super helpful for using DTOs (Data Transfer Objects), e.g. objects retrieved from an API or a Database.

A pure DTO should always represent the transferred data, i.e. we should never modify a DTO.

Immutable Objects + SQLite-NET

SQLite always required mutable objects (i.e. every property had to have a public get and a public set) because SQLite uses Reflection to create the objects it retrieves from our database.

But recently, Microsoft released C# 9.0 which introduced Init-Only Setters!

This allows us to define properties that can be set during initialization, AND cannot be changed. In other words init-only setters let us create immutable objects, and EVEN BETTER, they allow Reflection to create Immutable Objects! 🎉

Immutable Object using Init-Only Setters

public class Person
{
    public string FirstName { get; init; } //Read-only Property, can be set during initialization, but cannot be changed after initialization
    public string LastName { get; init; } //Read-only Property, can be set during initialization, but cannot be changed after initialization
}

This allows properties to be set during initialization AND does not allow the properties to be changed.

var brandon = new Person
{
    FirstName = "Brandon",
    LastName = "Minnick"
};

// The values of `brandon` can never be changed 
// I.e. `person.FirstName = "Kim"` now causes a compiler error

And the best part is that it is ALSO valid for SQLite because Reflection can still set the properties during initialization!!!

Example

If you're looking for a completed sample, check out this code from InvestmentDataSampleApp.

using SQLite;

static class Program
{
    public static async Task Main()
    {
    	var databasePath = Path.Combine(Xamarin.Essentials.FileSystem.AppDataDirectory, "Database.db3");
        var databaseConnection = new SQLiteAsyncConnection(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache);
        await databaseConnection.CreateTablesAsync(CreateFlags.None, typeof(PersonModel));
            
        // Save Immutable Object to Database
        await databaseConnection.InsertOrReplaceAsync(new Person
        {
            FirstName = "Brandon",
            LastName = "Minnick"
        });
            
        // Save Immutable Object to Database
        await databaseConnection.InsertOrReplaceAsync(new Person
        {
            FirstName = "James",
            LastName = "Montemagno"
        });
            
        //Retrieve all immutable obects from Database, placing it into a read-only List (e.g. an immutable List)
        IReadonlyList<PersonModel> allPeople = await databaseConnection.Table<PersonModel>().ToListAsync();        
    }
}

class PersonModel
{
    [PrimaryKey, AutoIncrement] //PrimaryKey must be nullable: https://github.com/praeclarum/sqlite-net/issues/327
    public int? ID { get; init;}
    public string FirstName { get; init; }
    public string LastName { get; init; }
}