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!
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; }
}