Mono.Data.SQLiteで使えるバリューオブジェクトの永続化ライブラリ

はじめに

Mono.Data.SQLiteを使う簡単なバリューオブジェクトの永続化ライブラリを作りました.
http://github.com/reinforce-lab/net.ReinforceLab.MonoTouch.Controls の SQLitePersistence にコードを置きました.また簡単なテストを SQLitePersistence.Test に置いています.

これはGoogle Code Archive - Long-term storage for Google Code Project Hosting.SQL文の作成方法を参考にして,Mono.Data.SQLiteを使うように0ベースで書いたものです.

このライブラリを作ったわけ

目的はMonoTouchでデータベースへのアクセスを分かりやすいコードで書くことです.
この目的にGoogle Code Archive - Long-term storage for Google Code Project Hosting. が適していたのですが,iPhoneシミュレータおよび実機向けコンパイル時に,確保していないメモリ領域解放をする,SQLiteに問い合わせできない,ARM向けネイティブコードにコンパイル失敗する,などの問題が発生したために,同等のライブラリを探しました.
できればO/Rマッピングツール(S2Dao - リファレンス, NHibermate)を使いたいのですが,JITが使えないMonoTouchの制限からこれらを使うことは難しいと思いました (原理的に無理だと考えて判断したただけで,コードの確認および動作確認などの確認は取っていません)

ライブラリの内容

Mono.Data.SQLiteSQLiteへのアクセス手段をADO.NETで提供します.ADO.NETはデータテーブルと行を,DataTable, DataRowなどのクラスで表します.このライブラリは,DataRowのアクセスをバリューオブジェクトのプロパティへのアクセスに見かける手段を提供します.具体的には,バリューオブジェクトの型情報を基にして,テーブル生成およびInsert/Update/DeleteなどのSQLコマンドを自動生成して操作を隠蔽し,バリューオブジェクトをインスタンスしているだけのように見せかけます.

使い方 紹介

ここではテストクラスにある簡単なサンプルを通して,使い方を紹介します.このライブラリの使い方は:

  • バリューオブジェクトのインタフェースを定義
  • バリューオブジェクトのデータアクセスクラス (先ほどのインタフェースの実装クラス)を宣言
  • データベースに接続

となっています.

インタフェースを定義するか,クラスを定義するか

このライブラリは,型情報から取得した読み出し可能なプロパティ情報に基づいてSQLテーブルを定義します.ですからSQLテーブルの元になる型は,インタフェースまたは実装クラスのいずれでもよいです.
ここではバリューオブジェクトとデータアクセスクラスとを明確に区別するために,バリューオブジェクトをインタフェースで,データアクセスをクラスで,それぞれ定義するサンプルを紹介します.
プロパティとDataRowのインデックスに使う文字列のミスタイプが気になる場合には,データアクセスクラスをSQLテーブルの元に使うとよいかもしれません.

インタフェースの定義

まずバリューオブジェクトのプロパティをインタフェース宣言にまとめます.

    public enum ArbEnumType { One, Two, Three }
    public interface IPrimitiveValues
    {
        [PrimaryKey]
        [AutoIncrement]
        int ID { get; }

        Byte ByteValue { get; set; }
        String StringValue { get; set; }
        DateTime DateTimeValue { get; set; }
        System.Int32 IntValue { get; set; }
        Double DoubleValue { get; set; }        
        ArbEnumType ArbVal {get; set;}             
    }
実装クラスを作る

次に先ほどのインタフェースの実装クラスを作ります

   public class PrimitiveValuesDAO : IPrimitiveValues
    {
        #region IPrimitiveValues メンバ
        public int ID {get { return (int)_row["ID"]; }}
        public byte ByteValue
        {
            get{return Convert.ToByte(_row["ByteValue"]);}            
            set{_row["ByteValue"] = value;}
        }
        public string StringValue
        {
            get{return (String)_row["StringValue"];}            
            set{_row["StringValue"] = value;}
        }
        public DateTime DateTimeValue
        {
            get {return (DateTime)_row["DateTimeValue"];}
            set {_row["DateTimeValue"] = value;}
        }
        public System.Int32 IntValue
        {
            get {return Convert.ToInt32(_row["IntValue"]);}
            set {_row["IntValue"] = value;}
        }
        public double DoubleValue
        {
            get{return Convert.ToDouble(_row["DoubleValue"]);}
            set{{_row["DoubleValue"] = value;}}
        }
        public ArbEnumType ArbVal
        {
            get{return (ArbEnumType)Convert.ToInt32(_row["ArbVal"]);}
            set{_row["ArbVal"] = (int)value;}
        }
        #endregion

        #region Constructor
        readonly DataRow _row;
        public PrimitiveValuesDAO(DataRow row)
        {
            _row = row;
        }
        #endregion                 
    }

コードを見ていただければ分かるとおり,DataRowのインデックサへのget/setをプロパティで隠蔽しただけのものです.ただしSQLiteは1~8バイトの整数をINTEGERという1つの変数型で扱うために,整数を保持するDataRowのColomnDataTypeはInt64になります.このために,キャストしただけでは(上記のコードでは,例えば, (Byte)DataRow["ByteValue"]) InvalidCastExceptionが発生するため,必ずたConvert.ToByte()メソッドなどConvertクラスの変換メソッドを使います.

データベースにアクセス

これで準備は完成です.バリューオブジェクト(IPrimitiveValues)の読み書きをしてみます.
まずSQLiteのデータベースに接続してOpenします.

var tmpFile = System.IO.Path.GetTempFileName();
var connection = new SQLiteConnection(string.Format("Data Source={0}", tmpFile));
connection.Open();

次にSQLitePersisitenceManagerを作り,CreateTableメソッドを呼びます.このメソッドは,指定されたインタフェースのプロパティ名に対応する行を持つテーブルを,まだ作られていなければ,作ります.

var pman = new SQLitePersistenceManager(connection);
pman.CreateTable<IPrimitiveValues>();

あとはSQLitePersistenceManagerクラスインスタンスのGetAdapterメソッドでSQLitedDataAdapterを取得して,テーブルに対してデータ操作を行うだけです.DataAdapterには適切なSQLコマンドがあらかじめ設定されていますから,テーブルの各行は,先ほどの実装クラスを通してバリューオブジェクトの配列として扱うことができます.ちなみに内部にあるSQLのテーブル名はインタフェースの型名,行名はプロパティ名になります.

using (var adapter = pman.GetAdapter<IPrimitiveValues>( "SELECT * FROM " + typeof(IPrimitiveValues).Name))
{                
  var tbl = new DataTable();                
  adapter.Fill(tbl);                
  tbl.Columns["ByteValue"].DataType = typeof(Byte);
  for (int i = 0; i < 10; i++)
  {                    
    var row = tbl.NewRow();                                        
    var dao = new PrimitiveValuesDAO(row);
    //DAOの値を設定していく
    dao.IntValue = i;                                        
    tbl.Rows.Add(row);
  }                                
  adapter.Update(tbl);            
}

変更した行の内容を確認してみます.

using (var adapter = pman.GetAdapter<IPrimitiveValues>("SELECT * FROM " + typeof(IPrimitiveValues).Name))   
{
  var tbl = new DataTable();                
  adapter.Fill(tbl);                

  Assert.AreEqual(10, tbl.Rows.Count);
  for(int i =0; i < 10; i ++)
  {
    var dao = new PrimitiveValuesDAO(tbl.Rows[i]);
                    Assert.AreEqual(i, dao.IntValue);                                    
  }            
}            

まとめ

MonoTouchで使える簡単なO/Rマッピングもどきのライブラリを作りました.型情報を元にしてSQL操作を隠蔽して,SQLのテーブル操作をバリューオブジェクトの配列操作のように見せかけます.こ
のライブラリの制約として,バリューオブジェクトのプロパティには基本型のみが指定でき,オブジェクトは指定できないことです.JSONなどを使いオブジェクトのシリアライズをかませれば,検索などの操作はできなくなりますが,任意の型のプロパティを持たせることはできます.これは今後実装するかもしれません.