SQLite.NETのExecuteScalarにはまる

落とし穴の存在も対処方法も分かっているはずなのに、いつも決まってはまってしまう問題がある。今回もその一例。

SQLite.NETを使ってゴリゴリとSQLを書き、テストコードを書いて実行したところでInvalidCastExceptionが発生した。開発環境は下記の通り。

using (SQLiteConnection connection = new SQLiteConnection("Data Source=test.db"))
{
    connection.Open();
    using (SQLiteCommand command = new SQLiteCommand(connection))
    {
        command.CommandText = "CREATE TABLE IF NOT EXISTS Foo(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, value INTEGER)";
        command.ExecuteNonQuery();

        // Insert data
        SQLiteParameter[] parameters = new SQLiteParameter[]{
         new SQLiteParameter("@value", 100)
        };
        command.CommandText = "INSERT INTO Foo(value) VALUES (@value)";
        command.Parameters.AddRange(parameters);
        int inserted = command.ExecuteNonQuery();
        Debug.Assert(inserted == 1, "Inserted rows:" + inserted);

        command.CommandText = "SELECT count(*) FROM Foo";
        int total1 = Convert.ToInt32(command.ExecuteScalar());
        Debug.Assert(total1 == 1, "Total rows (1):" + total1);

        command.CommandText = "SELECT sum(value) FROM Foo";
        int values1 = Convert.ToInt32(command.ExecuteScalar());     // Sucess
        Debug.Assert(values1 == 100, "Total values (1):" + values1);

        // Delete data
        command.CommandText = "DELETE FROM Foo";
        int deleted = command.ExecuteNonQuery();
        Debug.Assert(deleted == 1, "Deleted rows:" + deleted);

        command.CommandText = "SELECT count(*) FROM Foo";
        int total2 = Convert.ToInt32(command.ExecuteScalar());
        Debug.Assert(total2 == 0, "Total rows (2):" + total2);

        command.CommandText = "SELECT sum(value) FROM Foo";
        //int values2 = Convert.ToInt32(command.ExecuteScalar()); // <- InvalidCastException
        object obj = command.ExecuteScalar();
        int values2;
        if (obj == null || obj == DBNull.Value)
        {
            values2 = 0;
        }
        else
        {
            values2 = Convert.ToInt32(obj);
        }
        Debug.Assert(values2 == 0, "Total values (2):" + values2);
    }
}

ExecuteScalarについてMSDNには下記のように書かれていて、確かに理屈通りの動作と言うことは分かるのだけど、ここでややこしいのは、データが存在しない時、count()の方は正常に数値0が返るのに、sum()ではDBNullが返るという点だ。

結果セットの最初の行の最初の列。結果セットが空の場合は、null 参照 (Visual Basic の Nothing)。

SqlCommand.ExecuteScalar Method (System.Data.SqlClient) | Microsoft Docs

似たような計算を行っているのだから、勝手な要望を言えば「どちらも0を返す」か「どちらもDBNullを返す」のいずれかに統一して欲しい気もする(もちろん、APIとしての整合性が合わなくなってしまうことは重々承知だけど)。悲しいことに、この辺りのコードを時々しか書かないと、どちらが成功する処理でどちらが失敗する処理だったのか咄嗟には思い出せない。掲示板を見るとこの類の話題に事欠かないれど、そのような質問をする人の気持ちは良く分かるような気がしている。



関連