Xamarin.iOS での Microsoft.Data.Sqlite の利用

Microsoft.Data.Sqlite は、Xamarin で SQLite を使うときの定番になっている sqlite-net-pcl と同じく、SQLitePCL.raw に依存している。 sqlite-net-pcl が Xamarin.iOS で利用できるということは、Microsoft.Data.Sqlite も利用できるに違いない。

サンプルを書いて試してみた。

using System;
using System.Collections.Generic;
using System.IO;
using Foundation;
using Microsoft.Data.Sqlite;
using UIKit;
using Xamarin.Essentials;

namespace HelloSqlite
{
    public class Application
    {
        static void Main(string[] args)
        {
            UIApplication.Main(args, null, "AppDelegate");
        }
    }

    [Register("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        public override UIWindow Window { get; set; }

        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            SQLitePCL.Batteries_V2.Init();

            Window = new UIWindow(UIScreen.MainScreen.Bounds);
            Window.RootViewController = new UINavigationController(
                new ItemsViewController());
            Window.MakeKeyAndVisible();

            return true;
        }
    }

    public class Item
    {
        public int Id { get; set; }

        public string Content { get; set; }
    }

    public class ItemsViewController : UITableViewController
    {
        List<Item> items = new List<Item>();

        public ItemsViewController()
            : base(UITableViewStyle.Plain)
        {
            Title = "HelloSqlite";
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            NavigationItem.RightBarButtonItem = new UIBarButtonItem(
                UIBarButtonSystemItem.Add, HandleAdd);

            CreateTable();
            LoadItems();
        }

        public override nint RowsInSection(UITableView tableView, nint section)
        {
            return items.Count;
        }

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            const string cellId = "ItemCell";
            var cell = tableView.DequeueReusableCell(cellId) ??
                new UITableViewCell(UITableViewCellStyle.Default, cellId);
            var item = items[indexPath.Row];
            cell.TextLabel.Text = item.Content;
            return cell;
        }

        void HandleAdd(object sender, EventArgs e)
        {
            AddItem();
            LoadItems();
        }

        void CreateTable()
        {
            using (var connection = CreateConnection())
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = @"
                        CREATE TABLE IF NOT EXISTS items (
                            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                            content TEXT NOT NULL
                        );";
                    command.ExecuteNonQuery();
                }
            }
        }

        void AddItem()
        {
            using (var connection = CreateConnection())
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = $@"
                        INSERT INTO items (
                            content
                        ) VALUES (
                            '{DateTime.Now}'
                        )";
                    command.ExecuteNonQuery();
                }
            }
        }

        void LoadItems()
        {
            var nextItems = new List<Item>();
            using (var connection = CreateConnection())
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = @"
                        SELECT id, content
                        FROM items
                        ORDER BY id";
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            var item = new Item
                            {
                                Id = reader.GetInt32(0),
                                Content = reader.GetString(1),
                            };
                            nextItems.Add(item);
                        }
                    }
                }
            }

            items = nextItems;
            TableView.ReloadData();
        }

        SqliteConnection CreateConnection()
        {
            var builder = new SqliteConnectionStringBuilder();
            builder.DataSource = Path.Combine(
                FileSystem.AppDataDirectory,
                "HelloSqlite.db");
            return new SqliteConnection(builder.ToString());
        }
    }
}

iOS のシミュレーターだと動的コード生成を行うプログラムであっても動かせてしまうので、実機で動くことを確認するまでは安心できない。

このサンプルを実機に転送して実行したところ、期待通り SQLite データベースにデータを登録できた。

Microsoft.Data.Sqlite が使えたということは、 ADO.NET の抽象化の上に構築されたライブラリが Xamarin.iOS でも使える、 ということになる。 ただし、動的コード生成を行っていなければね。 このあいだ Dapper のソースコードを読んだら DynamicMethod 使っていたんだよなぁ。 残念。