Using EntityFramework Core and SQLite with Xamarin Forms

Using EntityFramework Core and SQLite with Xamarin Forms

Data persistence plays a big role in almost every application there is. With the introduction of .NET Core and EntityFramework Core a new possibility arose when it comes to data persistence. Now it is possible to use EntityFramework Core to generate a local storage for our Xamarin Applications. In this article we will explain in a step-by-step tutorial how to set up a Xamarin Forms App with a SQLite local storage, and we are going to use EntityFramework Core to generate a Code-First Database and apply that migration to our app.

Note: I assume that you have basic knowledge and have installed the .NET Core SDK, .NET Core tools EntityFramework Core, and all other tools needed to develop a Xamarin Forms app. If not, I suggest you get familiar with them first. I am using Rider as my IDE.

Resources:

https://docs.microsoft.com/en-us/ef/core/

https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet

https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/

Setting up the project

Let us go ahead and create a new Xamarin Forms App. Nothing fancy here, just do the usual project creation from the wizard. My project will be called XamarinEFCore.

Next thing, install Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.Sqlite in every project, the traditional and the class library.

Then, create a new class library which will contain all the class entities we will store in our database. Be careful when you select the target framework for the class library, it should be .NET Standard 2.0 and not .NETCore x.x .Also install the Microsoft.EntityFrameworkCore here as well. You will need it for data annotations.

Next, we will create our first entity. It is a pretty basic one just for demo purposes.

public class ListItem
{
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int Id { get; set; }
   public string RandomGuid { get; set; }
   public DateTime TimeAdded { get; set; }
}

The data annotation on top of the Id property serves to generate an auto-increment primary key. If you plan to handle unique keys yourself, then you can use the [Key] annotation.

Setting up the DatabaseContext

The next step is to reference the entities class project in the traditional class library. This is needed so when we setup the context we can define the data sets.

Then, we create a folder called DatabaseContext that will contain our context class. Our context class will look like this:

public class ApplicationDbContext : DbContext
{
    public DbSet<ListItem> Items { get; set; }
    private const string DatabaseName = "myItems.db";

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        String databasePath;
        switch (Device.RuntimePlatform)
        {
            case Device.iOS:
                databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "..", "Library", DatabaseName);
                break;
            case Device.Android:
                databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), DatabaseName);
                break;
            default:
                throw new NotImplementedException("Platform not supported");
        }
        optionsBuilder.UseSqlite($"Filename={databasePath}");
    }
}

We add a DbSet<Class> property for each entity that we will store in the database. Because every platform has a different storage location, we set the databasePath accordingly based on the device platform.

Next, we must do some work in the iOS project to make this work. In the AppDelegate.cs add this line before Xamarin.Forms.Init();

SQLitePCL.Batteries_V2.Init ();

On the Main.cs file add these two lines on top of the namespace

[assembly: Preserve(typeof(System.Linq.Queryable), AllMembers = true)]
[assembly: Preserve(typeof(System.DateTime), AllMembers = true)]
[assembly: Preserve(typeof(System.Linq.Enumerable), AllMembers = true)]

[assembly: Preserve(typeof(System.Linq.IQueryable), AllMembers = true)]

We need to tell the linker explicitly to include these assemblies, because it has no other way to know that EntityFramework Core needs them, and if we don’t the application will crash while we are migrating the database or if we try to use LINQ queries for data filtering.

As a last step, open the App.xaml.cs file and in the constructor add these two lines

var db = new ApplicationDbContext();

db.Database.Migrate();

This applies all the pending migrations to the database, and if it doesn’t exist it creates the database.

Creating a migration

To create a migration for our app to use we cannot use any of these projects sadly, so we need to create a new project in the same solution.

Let us create a console application this time that targets .NETCore so we can create a migration.

The next step is to change the default namespace the project has, to match the one of the shared class library. In my example the namespace will be changed to XamarinEFCore.

Then we install Microsoft.EntityFrameworkCore , Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore.Design .

We also need to add a reference to our Entites project.

Next, we will recreate the DatabaseContext folder and the ApplicationDbContext class in the MigrationCreator project. The only difference will be in the overridden method. It should look like this.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlite ($"Filename={DatabaseName}");
}

You are done! Congratulations

Now you only need to create the migration and output it to the traditional shared library of the application.

You do that from the command line. You navigate to the MigrationCreator folder which contains your .csproj file and execute the following command

dotnet ef migrations add -o <path-to-your-shared-lib> <MigrationName>

All set! Happy coding.

The full source code for the application sample can be found here.