反射建表
DB判断
csharp
// 构造函数:程序启动时自动运行
// 参数:默认数据库名 data.db,数据保留30天,CSV文件最大50M
public DataRepository(string dbFile = "data.db", int retentionDays = 30, long maxCsvBytes = 50 * 1024 * 1024)
{
// 1. 拼接数据库路径:把 data.db 放在程序根目录(工控软件标准路径)
_dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbFile);
// 2. 生成配套的CSV文件路径(数据库坏了用这个)
_csvPath = Path.ChangeExtension(_dbPath, ".csv");
// 专门存报警信息的CSV文件
_alarmsCsvPath = Path.ChangeExtension(_dbPath, ".alarms.csv");
// 3. 数据保留天数:最小1天(防止输入0/负数)
_retentionDays = Math.Max(1, retentionDays);
// CSV文件最大大小:最小1M(防止文件太小崩溃)
_maxCsvBytes = Math.Max(1024 * 1024, maxCsvBytes);
// ============== 核心逻辑:自动检测 SQLite 驱动 ==============
try
{
// 反射查找 SQLite 驱动(不直接引用,软依赖)
var t = Type.GetType("System.Data.SQLite.SQLiteConnection, System.Data.SQLite");
// 如果能找到驱动 → 启用 SQLite 数据库
if (t != null)
{
_useSqlite = true;
// 如果数据库文件不存在 → 创建一个空文件(表后面自动建)
if (!File.Exists(_dbPath))
{
File.WriteAllBytes(_dbPath, new byte[0]);
}
}
}
catch
{
// 找不到驱动/加载失败 → 禁用数据库,只用CSV文件
_useSqlite = false;
}
}
优先使用 SQLite 数据库存储(核心)
csharp
var conn = (IDisposable)Activator.CreateInstance(connType, $"Data Source={_dbPath};Version=3;");
//conn = 真实的、正在使用的数据库连接(水管 / 通道)
//connType = 水管图纸
//Activator.CreateInstance = 按照图纸做出一根真实水管
csharp
// 1. 反射获取数据库连接的 Open 方法(打开数据库)
var open = connType.GetMethod("Open");
//var = 让编译器自动识别变量类型
// 执行打开数据库连接
//它是从数据库图纸里,把「Open(打开)」这个方法的信息找出来。
//所以 OPEN就是单独的打开方法,不知道打开的内容;conn是打开的内容
open.Invoke(conn, null);
//Invoke 就是 反射专用的 "执行方法"
// 2. 反射动态创建 SQLite 命令对象(不用手动引用SQLite驱动)
var cmd = connType.Assembly.CreateInstance("System.Data.SQLite.SQLiteCommand", false,
System.Reflection.BindingFlags.CreateInstance, null, new object[] { }, null, null) as dynamic;
//空的数据库执行命令工具 = 刚造出来、啥都没配置、啥都干不了的「毛坯工具」
//叫它命令工具,就是因为它的类名叫 SQLiteCommand,它天生就是用来给数据库发送 SQL 命令的专用工具!
//false 意思:类名严格区分大小写,不能写错 → 必须写 SQLiteCommand,不能写 sqlitecommand
//BindingFlags.CreateInstance(核心)意思:告诉电脑 → 我要创建一个新的实例(造新工具) 这是反射造对象必须写的固定指令
//null 意思: 使用系统默认的绑定器 → 不用自定义复杂规则,电脑自动处理 不用管,工控代码永远写 null。
//new object[] { }(超级重要) 意思:调用无参构造函数 → 造工具的时候不传任何配件
// 3. 把命令绑定到数据库连接
// 最后两个 null 意思:语言格式、激活参数 → 系统默认,完全不用管工控反射代码固定写 null。
//as dynamic(最后一步关键) 把造出来的工具,转成动态类型 → 不用强制声明类型,直接点属性、调用方法 不加 as dynamic:你不能写 cmd.Connection、cmd.ExecuteNonQuery(),电脑不识别;加了之后:像正常代码一样随便用,编译不报错,运行自动匹配。cmd.Connection = conn;
// 4. SQL语句:如果没有Alarms报警表,就自动创建
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS Alarms(
Id INTEGER PRIMARY KEY AUTOINCREMENT, // 自增ID
Timestamp DATETIME, //-- 报警时间
State TEXT, // -- 报警状态
Message TEXT);"; // -- 报警内容
//cmd.CommandText = 往工具里写具体指令
cmd.ExecuteNonQuery(); // 执行建表语句
//cmd.ExecuteNonQuery() = 让数据库执行这个指令
// 5. SQL语句:插入报警数据(参数化写法,安全不报错)
cmd.CommandText = "INSERT INTO Alarms(Timestamp,State,Message) VALUES(@ts,@s,@m);";
// 给参数赋值:把设备报警的时间、状态、信息传进去
cmd.Parameters.AddWithValue("@ts", alarm.Time);
cmd.Parameters.AddWithValue("@s", alarm.DisplayState);
cmd.Parameters.AddWithValue("@m", alarm.Message);
//cmd.Parameters.AddWithValue 翻译:给占位符填真实数据
// 执行插入,把数据存进数据库
cmd.ExecuteNonQuery();
// 6. 反射获取 Close 方法,关闭数据库连接
var close = connType.GetMethod("Close");
close.Invoke(conn, null);
降级方案 → 写入 CSV 文本文件
csharp
try
{
// 加锁:防止多线程同时写文件,导致文件损坏
lock (_fileLock)
{
// 获取文件所在文件夹,不存在就创建
var dir=Path.GetDirectoryName(_alarmCsvPath);
if(!Directory.Exists(dir)) Directory.CreateDirectory(dir);
// 如果CSV文件不存在,创建文件+写入表头(时间,状态,信息)
if (!File.Exists(_alarmCsvPath))
{
File.WriteAllText(_alarmCsvPath, "Timestamp,State,Message\n", Encoding.UTF8);
}
// 转义双引号,防止CSV格式错乱
string escState=alarm.DisplayState.Replace("\"", "\"\"");
string escMsg=alarm.Message.Replace("\"", "\"\"");
// 拼接一行报警数据
var line = $"\"{alarm.Time:O}\",\"{escState}\",\"{escMsg}\"\n";
// 追加写入文件
File.AppendAllText(_alarmCsvPath, line, Encoding.UTF8);
}
}
catch { } // 最后一层防护,写入文件失败也不崩溃

HALCON安装
















