sqliteで管理してみた(2) - 実装

C#のsqlite実装

前回初期処理とProgEntryの読み出しの一部について紹介しましたが、ここでは使用するSQL文を全て実装していきます。

配列用の変換処理

ProgFolderではSubFoldersやNodesは":"区切りで配列的な役割をさせています。List<int> { 1, 2, 3 } → ":1:2:3:" というフォーマット形式にしています。ということで双方向の処理です。

// ":x:y:z:"形式からList<int> = {x, y, z}に変換
private static List<intParseArrange(string str)
{
    List<int> entry = new List<int>();
    if (string.IsNullOrEmpty(str)) { return entry; }
    string[] items = str.TrimStart(':').TrimEnd(':').Split(':');
    if (items.Length != 0)
    {
        foreach (string item in items)
        {
            entry.Add(int.Parse(item));
        }
    }
    return entry;
}

// List<int> = {x, y, z}形式から":x:y:z:"形式に変換
private static string ParseDearrange(List<int> items)
{
    if (items.Count == 0return string.Empty;
    StringBuilder sb = new StringBuilder(20);

    foreach (int entry in items)
    {
        sb.Append(string.Format(":{0}", entry));
    }

    sb.Append(":");
    return sb.ToString();
}

SELECT文

ProgEntry関連のSELECT文処理となります。

public static ProgEntry GetEntryInfo(int No)
{
    var sql = string.Format("SELECT * FROM ProgEntry WHERE No={0};", No);
    return GetEntrySingleInternal(sql);
}

public static ProgEntry GetEntryInfo(string Name)
{
    var sql = string.Format("SELECT * FROM ProgEntry WHERE Name=\'{0}\';", Name.Replace("'""''"));
    return GetEntrySingleInternal(sql);
}

public static List<ProgEntry> GetEntryInfo(int[] No)
{
    if (No.Length == 0) { return null; }
    StringBuilder sb = new StringBuilder(20);
    sb.Append("SELECT * FROM ProgEntry WHERE No IN (");
    foreach (int item in No)
    {
        sb.Append(string.Format("{0},", item));
    }
    var sql = string.Format("{0});", sb.ToString().Trim(','));
    return GetEntryMultiInternal(sql);
}

ProgFolder関連のSELECT文処理となります。

public static ProgFolder GetFolderInfo(int No)
{
    var sql = string.Format("SELECT * FROM ProgFolder WHERE No={0};", No);
    return GetFolderSingleInternal(sql);
}

public static ProgFolder GetFolderInfo(string path)
{
    var sql = string.Format("SELECT * FROM ProgFolder WHERE Name=\'{0}\';", path.Replace("'""''"));
    return GetFolderSingleInternal(sql);
}

public static List<ProgFolder> GetFolderInfo(int[] No)
{
    if (No.Length == 0) { return null; }
    StringBuilder sb = new StringBuilder(20);
    sb.Append("SELECT * FROM ProgFolder WHERE No IN (");
    foreach (int item in No)
    {
        sb.Append(string.Format("{0},", item));
    }
    var sql = string.Format("{0});", sb.ToString().Trim(','));
    return GetFolderMultiInternal(sql);
}

前回紹介した内容ではあるがProgEntryの実際の読み出し処理部です。

// 返値が1行であることを前提
private static ProgEntry GetEntrySingleInternal(string sql)
{
    ProgEntry entry = null;
    using (var cmd = new SQLiteCommand(sqlConn))
    {
        cmd.CommandText = sql;
        using (SQLiteDataReader sr = cmd.ExecuteReader())
        {
            if (sr.Read())
            {
                entry = InternalProgEntry(sr);
            }
        }
    }
    return entry;
}

// 返値が複数行であることを前提
private static List<ProgEntry> GetEntryMultiInternal(string sql)
{
    List<ProgEntry> entry = new List<ProgEntry>();
    using (var cmd = new SQLiteCommand(sqlConn))
    {
        cmd.CommandText = sql;
        using (SQLiteDataReader sr = cmd.ExecuteReader())
        {
            while (sr.Read())
            {
                entry.Add(InternalProgEntry(sr));
            }
        }
    }
    return entry;
}

private static ProgEntry InternalProgEntry(SQLiteDataReader sr)
{
    ProgEntry entry = new ProgEntry();
    entry.No = int.Parse(sr["No"].ToString());
    entry.Name = sr["Name"].ToString();
    entry.Links = int.Parse(sr["Links"].ToString());
    entry.Incompleted = (int.Parse(sr["Incomplete"].ToString()) == 1) ? true : false;

    return entry;
}

次にProgFolderの実際の読み出し処理部です。

// 返値が1行であることを前提
private static ProgFolder GetFolderSingleInternal(string sql)
{
    ProgFolder entry = null;
    using (var cmd = new SQLiteCommand(sqlConn))
    {
        cmd.CommandText = sql;
        using (SQLiteDataReader sr = cmd.ExecuteReader())
        {
            if (sr.Read())
            {
                entry = InternalProgFolder(sr);
            }
        }
    }
    return entry;
}

// 返値が複数行であることを前提
private static List<ProgFolder> GetFolderMultiInternal(string sql)
{
    List<ProgFolder> entry = new List<ProgFolder>();
    using (var cmd = new SQLiteCommand(sqlConn))
    {
        cmd.CommandText = sql;
        using (SQLiteDataReader sr = cmd.ExecuteReader())
        {
            while (sr.Read())
            {
                entry.Add(InternalProgFolder(sr));
            }
        }
    }
    return entry;
}

private static ProgFolder InternalProgFolder(SQLiteDataReader sr)
{
    ProgFolder entry = new ProgFolder();
    entry.No = int.Parse(sr["No"].ToString());
    entry.Name = sr["Name"].ToString().Replace("\\""/");
    entry.SubFolders = ParseArrange(sr["SubFolders"].ToString());
    entry.LastWrite = long.Parse(sr["LastWrite"].ToString());
    entry.Nodes = ParseArrange(sr["Nodes"].ToString());

    return entry;
}

また、最後にINSERTされたNoを取得するSQL文となります。この処理についてはこの関数のみで完結させてしまっています。

public static int LastInsert(string table)
{
    var sql = string.Format("SELECT * FROM {0} WHERE ROWID = LAST_INSERT_ROWID(); ", table);
    int No = -1;
    using (var cmd = new SQLiteCommand(sqlConn))
    {
        cmd.CommandText = sql;
        using (SQLiteDataReader sr = cmd.ExecuteReader())
        {
            sr.Read();
            No = int.Parse(sr["No"].ToString());
        }
    }
    return No;
}

INSERT/UPDATE文

既にデータが存在している時はUPDATEで処理させて、存在しない時はINSERTを実行する処理にしたいと思います。

ProgEntryのINSERT/UPDATE処理はUPDATE処理時にはLinks数を増やすことにします。

public static int InsertEntry(ProgEntry entry)
{
    ProgEntry buffer = GetEntryInfo(entry.Name);
    string sql = string.Empty;
    if (buffer == null)
    {
        // データが存在しないのでエントリ追加
        entry.Links = 1;
        sql = string.Format("INSERT INTO ProgEntry(Name, Links, Incomplete) VALUES(\'{0}\',{1},{2});",
            entry.Name.Replace("'""''"),
            entry.Links,
            entry.Incompleted ? 1 : 0);
    }
    else
    {
        // データが存在しているのでLinks数を増加
        sql = string.Format("UPDATE ProgEntry SET Links={0} WHERE No={1};", buffer.Links + 1, buffer.No);
    }

    if (ExecuteCmd(sql) != 1) { return -1; }
    if (buffer != null) { return buffer.No; }
    return LastInsert("ProgEntry");
}

ProgFolderの場合は既に存在している場合に変更はLastWrite、SubFolders、NodesカラムをUPDATE処理で、存在していない時はINSERT処理を実施しています。

public static int InsertFolder(ProgFolder entry)
{
    ProgFolder buffer = GetFolderInfo(entry.Name);
    string sql = string.Empty;
    if (buffer == null)
    {
        sql = string.Format("INSERT INTO ProgFolder(Name, LastWrite, SubFolders, Nodes) Values(\'{0}\', {1}, \'{2}\', \'{3}\');",
            entry.Name.Replace("\\""/"),
            entry.LastWrite,
            ParseDearrange(entry.SubFolders),
            ParseDearrange(entry.Nodes));
    }
    else
    {
        sql = string.Format("UPDATE ProgFolder SET LastWrite={0}, SubFolders=\'{1}\', Nodes=\'{2}\' WHERE No={3};",
            entry.LastWrite,
            ParseDearrange(entry.SubFolders),
            ParseDearrange(entry.Nodes),
            buffer.No);
    }

    if (ExecuteCmd(sql) != 1) { return -1; }
    if (buffer != null) { return buffer.No; }
    return LastInsert("ProgFolder");
}

DELETE文

ProgEntryとProgFolderの削除処理ですが、ProgEntryは削除させずにLinks数を減らしているのでUPDATEを使用しています。

public static int DeleteEntry(ProgEntry entry)
{
    var sql = string.Format("UPDATE ProgEntry SET Links={0} WHERE No={1};", entry.Links - 1, entry.No);
    return ExecuteCmd(sql);
}

public static int DeleteFolder(ProgFolder folder)
{
    var sql = string.Format("DELETE FROM ProgFolder WHERE No={0};", folder.No);
    return ExecuteCmd(sql);
}

以上の内容で外部から呼ぶ出せばデータベースに参照や書き込みがなされるはずです。

sqliteからデータの取り出し方には色々な方法がありますが、用途に合わせて試行錯誤が必要になるかと思います。


フォルダ削除処理においてSubFoldersやNodes等も合わせて処理するかを悩んだのですが、呼び出し元の方で対処することにしました。

また、結果的にはデータ構造に依存させる処理を組み込んでしまっているが、データベース処理部は最小限に抑えたかったというのも理由ではあります。

この記事が気に入ったらサポートをしてみませんか?