反射建表 DB判断 // 构造函数:程序启动时自动运行 // 参数:默认数据库名 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 数据库存储(核心) var conn= ( IDisposable) Activator. CreateInstance ( connType, $"Data Source= { _dbPath } ;Version=3;" ) ; //conn = 真实的、正在使用的数据库连接(水管 / 通道) //connType = 水管图纸 //Activator.CreateInstance = 按照图纸做出一根真实水管 // 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 文本文件 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安装