LINQ to SQLite を使っている中で、複数のテーブルに共通するカラム(列)がある場合、ヘルパー関数などをそれぞれのテーブルごとに手書きするのは大変だしメンテナンス性も悪い。ひとまとめにできないだろうか、というのを試行錯誤した結果、インターフェース+ジェネリックでいけることが分かったので整理しておく。
なお、LINQ to SQLite の基本的な事柄については「C# で SQLite を便利に使うサンプルコード(LINQ to SQLite)【改訂版】」を参照。
テーブル構造
フルーツの一覧を格納するフルーツテーブルと、肉の一覧を格納する肉テーブルがあるとする。フルーツテーブルと肉テーブルそれぞれのカラムのうち、ID と名前については両者共にカラムがあるものとする。
この場合、テーブル構造を定義するクラスは以下のようにする。
まず、共通カラム部分をインターフェースとしてまとめる。
public interface IFoodData
{
// ID
Int32 Id { get; set; }
// 名前
String Name { get; set; }
}
そのうえで、テーブル構造を定義するクラスにインターフェースを実装するようにする。例えばフルーツテーブルなら
[Table(Name = "t_fruit")]
public class TFruitData : IFoodData
{
// --------------------------------------------------------------------
// IFoodData 実装
// --------------------------------------------------------------------
// ID
[Column(Name = "fruit_id", DbType = LinqUtils.DB_TYPE_INT32, CanBeNull = false, IsPrimaryKey = true)]
public Int32 Id { get; set; }
// 名前
[Column(Name = "fruit_name", DbType = LinqUtils.DB_TYPE_STRING, CanBeNull = false)]
public String Name { get; set; }
// --------------------------------------------------------------------
// TFruitData 独自項目
// --------------------------------------------------------------------
// 色
[Column(Name = "fruit_color", DbType = LinqUtils.DB_TYPE_STRING, CanBeNull = true)]
public String Color { get; set; }
}
のようにする。
共通カラムの操作
フルーツテーブルと肉テーブルに共通するカラム(ID、名前)について、操作をジェネリックでまとめることができる。例えば、名前でレコードを検索・表示する関数は以下のようになる。
private void QueryFoodByName<T>(String oKeyword) where T : class, IFoodData
{
Console.WriteLine(LinqUtils.TableName(typeof(T)) + " 内で名前に「" + oKeyword + "」を含むレコードを検索");
using (SQLiteConnection aConnection = CreateDatabaseConnection(DB_NAME_GENERIC))
using (DataContext aContext = new DataContext(aConnection))
{
Table<T> aTableTest = aContext.GetTable<T>();
IQueryable<T> aQueryResult =
from x in aTableTest
where x.Name.Contains(oKeyword)
select x;
Console.WriteLine("検索結果:" + aQueryResult.Count() + " 件");
foreach (T aRecord in aQueryResult)
{
Console.WriteLine(aRecord.Name);
}
}
}
関数を呼びだす際は、
QueryFoodByName<TFruitData>("hoge");
QueryFoodByName<TMeatData>("fuga");
のようにする。
以上により、テーブル名が異なる複数のテーブルに共通するカラムの操作を、1 つのコードにまとめることができた。
継承でやってはどうか?
テーブル構造を定義するクラスを、インターフェースではなく継承で作るのはどうだろうか。
つまり、IFoodData をインターフェースではなく通常のクラスとして宣言してそれを基底クラスとし、TFruitData や TMeatData をそこから派生させる形にする。
しかし残念ながらそれはうまくいかなかった。
データベース内にテーブルを作成する段階で、「型 'TestLinqToSqlite.IFoodData' のデータ メンバー 'Int32 Id' は型 'TFruitData' のマッピングの一部ではありません。メンバーは継承階層のルートより上のメンバーですか?」というようなエラーが発生してしまう。
サンプルコード
サンプルコードは GitHub にて。サンプルコードアプリの使い方
なお、サンプルコードアプリには基本操作の欄もあるが、こちらについては「C# で SQLite を便利に使うサンプルコード(LINQ to SQLite)【改訂版】」を参照。
参考資料
更新履歴
- 2019/04/09 初版。
- 2022/01/03 参考資料のリンクを改訂版のものに更新。