将记录插入 SQLite 表时,我遇到了一个奇怪的错误。这是来自客户的日志:
Microsoft.Data.Sqlite.SqliteException: SQLite Error 19: 'NOT NULL constraint failed: Events.details'.
at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader()
at Microsoft.Data.Sqlite.SqliteCommand.ExecuteNonQuery()
at SqliteDemo.LogEvent(...) in ...
诊断的相关位显然是这样的:
SQLite Error 19: 'NOT NULL constraint failed: Events.details'
…这似乎很清楚,除了一件事:该details
列缺少NOT NULL
约束:
CREATE TABLE IF NOT EXISTS Events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name STRING NOT NULL,
time INTEGER NOT NULL,
code INTEGER NOT NULL,
details STRING,
message STRING NOT NULL,
status INTEGER NOT NULL )
我不知道插入失败的值,因此下一步显然是改进我的诊断并等待它再次发生。或者我可以添加空守卫以确保它不再发生并忘记整个事情。
但我很好奇:
- SQLite 的诊断是否有可能出错,并且它是
NOT NULL
真正的罪魁祸首之一? - 为什么我的单元测试没有空
detail
值问题? - 这可能与 SQLite 的动态输入方法有关吗?
- ……或者完全不同的东西?
有任何想法吗?
环境
- 面向 Windows 10 上的 .NET Framework 4.8
- 使用
Microsoft.Data.Sqlite
5.0.14
(不是System.Data.SQLite
——尽管我不确定区别是否相关)。
代码
我不能在这里发布我们的生产代码。相反,这是一个完整的、独立的 C# 程序,我相信它在所有相关方面都与生产代码非常相似。
using System;
using Microsoft.Data.Sqlite;
class SqliteDemo
{
static void Main()
{
SqliteDemo demo = new SqliteDemo();
demo.LogEvent("name 1", DateTime.Now, 42, "details", "message 1", 0);
demo.LogEvent("name 2", DateTime.Now, 42, null, "message 2", 0); //*** Works...
}
readonly string connectionString = new SqliteConnectionStringBuilder
{
DataSource = "Test.db",
Mode = SqliteOpenMode.ReadWriteCreate,
}.ConnectionString;
const string CreateTableSql =
@"CREATE TABLE IF NOT EXISTS Events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name STRING NOT NULL,
time INTEGER NOT NULL,
code INTEGER NOT NULL,
details STRING,
message STRING NOT NULL,
status INTEGER NOT NULL
)";
int LogEvent(string name, DateTime time,
int code, string details, string message, short status)
{
using (SqliteConnection connection = CreateOpenConnection())
{
using (SqliteCommand command = CreateCommand(connection,
"INSERT INTO Events(name, time, code, details, message, status) " +
"VALUES(@name, @time, @code, @details, @message, @status)"))
{
AddParameter(command, "name", name);
AddParameter(command, "time", time);
AddParameter(command, "code", code);
AddParameter(command, "details", details);
AddParameter(command, "message", message);
AddParameter(command, "status", status);
return command.ExecuteNonQuery(); //*** SOMETIMES throws
}
}
}
static void AddParameter(SqliteCommand command, string name, object value)
{
command.Parameters.Add(new SqliteParameter(name, value ?? DBNull.Value));
}
static SqliteCommand CreateCommand(SqliteConnection connection, string sql)
{
SqliteCommand command = connection.CreateCommand();
command.CommandText = sql;
return command;
}
SqliteConnection CreateOpenConnection()
{
SqliteConnection connection = new SqliteConnection(connectionString);
connection.Open();
CreateTableIfNecessary(connection);
return connection;
}
void CreateTableIfNecessary(SqliteConnection connection)
{
using (SqliteCommand command = CreateCommand(connection, CreateTableSql))
{
command.ExecuteNonQuery();
}
}
}