GraphMindStudio 数据操作层解析:基于 SQLite 的封装与自动化存储

在 Unity 项目开发中,经常需要与本地数据库(如 SQLite)进行交互。直接使用 System.Data.SQLite 的原始 API 会导致大量重复代码,且容易出错。GraphMindStudio.Data 命名空间下提供了一套封装完善的静态类库,旨在简化 SQLite 的常用操作,并提供自动化存储机制。本文将深入剖析该库的设计思路、核心功能及使用方法。

一、整体架构

该库主要包含两大静态类:

DataBase:核心数据库操作类,内部嵌套了多个功能模块,分别负责连接管理、表结构维护、数据增删改查、多表协同等。

AutoStorage:基于 DataBase 实现的自动化存储工具,支持动态列扩展、批量保存和灵活加载,特别适合存储结构不固定的数据。

所有类均使用静态成员,无需实例化,直接通过类名调用方法,符合工具类设计习惯。

二、数据库连接与基础操作(DataUniversal)

DataUniversal 是所有数据库操作的基石,负责建立连接、执行 SQL 并处理异常。

  1. 连接管理
    SetConnect():根据预设的数据库路径(默认位于 StreamingAssets/DataBase/MainDataBase.db)创建并打开 SQLiteConnection。路径可自定义修改 DataBasePath 静态字段。

连接字符串使用 "Data Source=...;Version=3;" 格式,确保兼容性。

  1. 核心执行方法
    ExecuteNonQuery:执行无返回结果的 SQL(如 INSERT、UPDATE、DELETE),支持参数化查询。

QueryRow:查询单行数据,并通过回调处理读取到的 SQLiteDataReader。

ExecuteSQL:通用执行模板,接受 SQL 语句、参数和操作委托,统一管理连接、命令和异常。

ExecuteQueryCore 系列:封装了 ExecuteReader 的循环读取逻辑,提供 readAllRows 参数控制是读取全部行还是仅第一行。

所有方法均使用参数化查询,有效防止 SQL 注入。异常信息被重新包装为包含具体 SQL 语句的 Exception,便于调试。

三、数据库与表的创建管理(DataCreate)

DataCreate 专注于数据库文件和表结构的初始化。

CreateDataBase:若数据库文件不存在,则调用 SQLiteConnection.CreateFile 创建空文件。

DoesTableExist:查询 sqlite_master 表判断指定表是否存在。

CreateTable:如果表不存在,则执行传入的表创建 SQL。

DropTable:安全删除表(DROP TABLE IF EXISTS)。

四、数据保存与删除(DataSave)

DataSave 提供了插入单行和按条件删除行的简便方法。

SaveSingleRow:根据字典参数动态生成 INSERT 语句,列名和值均来自字典的键和值。返回影响行数,若为 0 则抛出异常。

DeleteRow:按指定列的值删除行,返回影响行数。

CheckFirstColumnForValue:检查表的第一列(通常为主键或标识列)是否存在指定值,常用于存在性验证。

GetFirstColumnName:通过 PRAGMA table_info 获取表的第一列名,用于辅助其他操作。

五、数据读取(DataRead)

DataRead 封装了各种查询场景,返回格式化的数据。

GetTableColumns:获取表的所有列名列表。

ReadCompositeData:读取指定列的所有行,返回 List<List>,每行一个子列表。

GetRowValue:按条件查找单行,返回该行所有列值的字符串列表。

GetRowColumnValue:按条件查找单行中某一列的值。

SQLiteGetTableNames:获取数据库中所有用户表的名称。

ReadColumn:读取某一列的所有值(忽略其他列)。

所有读取方法均自动处理 DBNull,并转换为 null 或空字符串。

六、数据更新与结构修改(DataUpdate)

DataUpdate 不仅支持数据更新,还提供了表结构变更的辅助方法。

  1. 数据更新

    UpdateExistingRowByName:按 RowName 列更新指定行,参数为列名-新值的字典。

  2. 表结构变更

    CopyTable:复制源表结构及数据到新表(自动创建目标表)。

RenameColumn / AddColumn:调用 SQLite 的 ALTER TABLE 实现列重命名和添加。

ReorderColumns:重新排序列的顺序。实现方式:创建临时表,按新顺序定义列,将原数据按新列顺序插入,再替换原表。

ReorderRows:按指定的第一列值顺序重新排序行。同样采用临时表策略,遍历新顺序依次插入。

这些结构修改操作在 SQLite 原生不支持直接修改列顺序的背景下,通过临时表迁移实现了"伪重排"。

七、多表操作(MutipleData)

MutipleData 内部嵌套了 MutipleDataRead 和 MutipleDataUpdate,用于处理涉及多表的批量操作。

ReadRowsByColumnValue:读取指定表中满足某列值的所有行,每行以 Dictionary<string, object> 返回(列名→值)。

MoveRowsByColumnValue:将源表中满足条件的所有行移动到目标表。过程为先读取所有匹配行,逐行插入目标表,最后从源表删除这些行。该操作在单个事务中执行,保证原子性(但代码中未显式开启事务,需注意)。

八、自动化存储(AutoStorage)

AutoStorage 是一个高层次的封装,旨在让开发者无需关心表结构即可存储任意键值对数据。

  1. 表结构约定
    每个表默认包含 Id INTEGER PRIMARY KEY AUTOINCREMENT 作为自增主键。

额外列动态创建,列名由传入数据的键决定。

预留 RowName 列用于存储时间戳(记录插入时间)。

  1. 核心方法
    EnsureTable:确保表存在,若不存在则创建包含 Id 和默认 Content 列的表。

AddMissingColumns:检查现有列,为字典中未出现的列执行 ALTER TABLE ADD COLUMN。

SaveData:保存一个字典数据。自动添加时间戳到 RowName,忽略 Id 键,确保表结构包含所有列,最后执行 INSERT。

SaveDataBatch:批量保存多个字典。

LoadAllData:加载表中所有行,每行转换为 Dictionary<string, object>。

SaveString / SaveStrings:向下兼容的方法,将单个字符串或多个字符串存储为 Content 或 Content1、Content2......列。

LoadAllStrings:加载所有以 Content 开头的列的值,拼接成字符串列表返回。

AutoStorage 极大地简化了键值对数据的持久化,尤其适合存储配置、日志等动态扩展的数据。

十、总结与注意事项

GraphMindStudio.Data 提供了一套完整且易用的 SQLite 操作封装,具有以下优点:

参数化查询:所有 SQL 均使用参数,杜绝注入风险。

异常封装:统一捕获异常并附加上下文信息(如 SQL 语句),便于快速定位问题。

结构修改辅助:通过临时表实现了列顺序调整、行顺序重排等高级功能。

自动化存储:AutoStorage 大幅降低了对固定表结构的依赖,适合敏捷开发。

使用注意事项:

线程安全:静态类中的方法未加锁,不建议在多线程环境中同时操作同一数据库文件。

事务支持:批量操作(如 MoveRowsByColumnValue)未显式使用事务,若需原子性应在外层包裹事务。

路径问题:默认数据库路径位于 StreamingAssets,打包后为只读目录,写入数据时需注意路径可写性(可重定向到持久化路径)。

SQLite 版本:确保项目中引用的 System.Data.SQLite 版本支持所使用的 SQL 语法(如 RENAME COLUMN 需 SQLite 3.25.0+)。

csharp 复制代码
namespace GraphMindStudio.Data
{
    #region 数据库


    public static class DataBase
    {
        static public class DataUniversal
        {
            // 常量定义
            private const string DataBaseFolder = "DataBase";
            private const string MainDataBaseFileName = "MainDataBase.db";
            private const string ConnectionStringPrefix = "Data Source=";
            private const string ConnectionStringSuffix = ";Version=3;";
            private const string SqlExecutionFailedMessage = "SQL执行失败: {0}";
            private const string SqlQueryExecutionFailedMessage = "SQL查询执行失败: {0}\nSQL: {1}";
            private const string PragmaTableInfoTemplate = "PRAGMA table_info({0});";

            static public string DataBasePath = Path.Combine(UnityEngine.Application.streamingAssetsPath, DataBaseFolder, MainDataBaseFileName);

            static public SQLiteConnection SetConnect()
            {
                // 修正:使用 SQLiteConnection 而非 SQLiteException
                var conn = new SQLiteConnection($"{ConnectionStringPrefix}{DataBasePath}{ConnectionStringSuffix}");
                try { conn.Open(); }
                catch (Exception ex) { throw new Exception($"数据库连接失败: {ex.Message}", ex); }
                return conn;
            }

            public static void ExecuteNonQuery(string sql, Dictionary<string, object> parameters = null) =>
                ExecuteSQL(sql, parameters, cmd => cmd.ExecuteNonQuery());

            public static void QueryRow(string query, object valueToMatch, Action<SQLiteDataReader> handleRow) =>
                ExecuteSQL(query, new Dictionary<string, object> { { "@value", valueToMatch } }, cmd =>
                {
                    using var r = cmd.ExecuteReader();
                    if (r.Read()) handleRow(r);
                });

            public static void ExecuteSQL(string query, Dictionary<string, object> parameters, Action<SQLiteCommand> action)
            {
                using var conn = SetConnect();
                using var cmd = new SQLiteCommand(query, conn);
                if (parameters != null)
                    foreach (var p in parameters) cmd.Parameters.AddWithValue(p.Key, p.Value);
                try { action(cmd); }
                catch (Exception ex) { throw new Exception(string.Format(SqlExecutionFailedMessage, ex.Message), ex); }
            }

            static public void ExecuteQueryCoreWhile(string query, Action<SQLiteDataReader> buildRow, Dictionary<string, object> parameters = null) =>
                ExecuteQueryCore(query, buildRow, true, parameters);

            static public void ExecuteQueryCoreIf(SQLiteConnection _, string query, Action<SQLiteDataReader> buildRow, Dictionary<string, object> parameters = null) =>
                ExecuteQueryCore(query, buildRow, false, parameters);

            static public void ExecuteQueryCore(string query, Action<SQLiteDataReader> rowAction, bool readAllRows = true, Dictionary<string, object> parameters = null)
            {
                using var conn = SetConnect();
                using var cmd = new SQLiteCommand(query, conn);
                if (parameters != null)
                    foreach (var p in parameters) cmd.Parameters.AddWithValue(p.Key, p.Value);
                try
                {
                    using var r = cmd.ExecuteReader();
                    if (readAllRows) while (r.Read()) rowAction(r);
                    else if (r.Read()) rowAction(r);
                }
                catch (Exception ex) { throw new Exception(string.Format(SqlQueryExecutionFailedMessage, ex.Message, query), ex); }
            }

            public static List<string> GetTableColumnDefinitions(string tableName)
            {
                var defs = new List<string>();
                ExecuteQueryCoreWhile(string.Format(PragmaTableInfoTemplate, tableName), r =>
                {
                    string name = r["name"].ToString();
                    string type = r["type"].ToString();
                    string notnull = (long)r["notnull"] == 1 ? "NOT NULL" : "";
                    string dflt = r["dflt_value"] != DBNull.Value ? $"DEFAULT {r["dflt_value"]}" : "";
                    string pk = (long)r["pk"] == 1 ? "PRIMARY KEY" : "";
                    defs.Add($"{name} {type} {notnull} {dflt} {pk}".Trim());
                });
                return defs;
            }
        }

        static public class DataCreate
        {
            // 常量定义
            private const string CheckTableExistsQuery = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=@tableName;";
            private const string DropTableTemplate = "DROP TABLE IF EXISTS {0}";
            private const string TableNotExistMessage = "表 '{0}' 不存在。";

            static public void CreateDataBase()
            {
                if (!File.Exists(DataUniversal.DataBasePath))
                    SQLiteConnection.CreateFile(DataUniversal.DataBasePath);
            }

            static public bool DoesTableExist(string tableName)
            {
                long cnt = 0;
                DataUniversal.ExecuteSQL(CheckTableExistsQuery,
                    new Dictionary<string, object> { { "@tableName", tableName } },
                    cmd => cnt = (long)cmd.ExecuteScalar());
                return cnt > 0;
            }

            static public void CreateTable(string tableName, Func<string> createTableQuery)
            {
                if (!DoesTableExist(tableName))
                    DataUniversal.ExecuteNonQuery(createTableQuery());
            }

            static public void DropTable(string tableName) =>
                DataUniversal.ExecuteNonQuery(string.Format(DropTableTemplate, tableName));
        }

        static public class DataSave
        {
            // 常量定义
            private const string InsertIntoTemplate = "INSERT INTO {0} ({1}) VALUES ({2})";
            private const string DeleteFromWhereTemplate = "DELETE FROM [{0}] WHERE [{1}] = @value";
            private const string CheckFirstColumnExistsTemplate = "SELECT COUNT(1) FROM {0} WHERE \"{1}\" = @value";
            private const string PragmaTableInfoTemplate = "PRAGMA table_info({0})";
            private const string InsertFailedMessage = "插入数据失败: 表 {0}";
            private const string DeleteFailedMessage = "删除数据失败: 表 {0}, 条件 {1}={2}";
            private const string TableNameEmptyMessage = "表名不能为空";
            private const string ValueToMatchEmptyMessage = "匹配值不能为空";
            private const string CannotGetFirstColumnMessage = "无法获取表 {0} 的第一列名称";

            static public int SaveSingleRow(string tableName, Dictionary<string, object> parameters)
            {
                string cols = string.Join(", ", parameters.Keys);
                string vals = string.Join(", ", parameters.Keys.Select(k => $"@{k}"));
                int rows = 0;
                DataUniversal.ExecuteSQL(string.Format(InsertIntoTemplate, tableName, cols, vals), parameters,
                    cmd => rows = cmd.ExecuteNonQuery());
                if (rows == 0) throw new Exception(string.Format(InsertFailedMessage, tableName));
                return rows;
            }

            public static int DeleteRow(string tableName, string rowName, string valueToMatch)
            {
                int rows = 0;
                DataUniversal.ExecuteSQL(string.Format(DeleteFromWhereTemplate, tableName, rowName),
                    new Dictionary<string, object> { { "@value", valueToMatch } },
                    cmd => rows = cmd.ExecuteNonQuery());
                if (rows == 0) throw new Exception(string.Format(DeleteFailedMessage, tableName, rowName, valueToMatch));
                return rows;
            }

            static public bool CheckFirstColumnForValue(string tableName, string valueToMatch)
            {
                if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentException(TableNameEmptyMessage, nameof(tableName));
                if (string.IsNullOrWhiteSpace(valueToMatch)) throw new ArgumentException(ValueToMatchEmptyMessage, nameof(valueToMatch));
                string firstCol = GetFirstColumnName(tableName);
                long cnt = 0;
                DataUniversal.ExecuteSQL(string.Format(CheckFirstColumnExistsTemplate, tableName, firstCol),
                    new Dictionary<string, object> { { "@value", valueToMatch } },
                    cmd => cnt = (long)cmd.ExecuteScalar());
                return cnt > 0;
            }

            static public string GetFirstColumnName(string tableName)
            {
                using var conn = DataUniversal.SetConnect();
                using var cmd = new SQLiteCommand(string.Format(PragmaTableInfoTemplate, tableName), conn);
                using var r = cmd.ExecuteReader();
                if (r.Read()) return r["name"].ToString();
                throw new InvalidOperationException(string.Format(CannotGetFirstColumnMessage, tableName));
            }
        }

        static public class DataRead
        {
            // 常量定义
            private const string PragmaTableInfoTemplate = "PRAGMA table_info({0})";
            private const string SelectColumnsFromTableTemplate = "SELECT {0} FROM {1}";
            private const string SelectAllFromTableWhereTemplate = "SELECT * FROM [{0}] WHERE [{1}] = @value LIMIT 1";
            private const string SelectColumnFromTableWhereTemplate = "SELECT [{0}] FROM [{1}] WHERE [{2}] = @value LIMIT 1";
            private const string SelectAllTablesQuery = "SELECT name FROM sqlite_master WHERE type='table';";
            private const string NoDataFoundMessage = "未找到匹配的数据: 表 {0}, 条件 {1}={2}";

            static public List<string> GetTableColumns(string tableName)
            {
                var cols = new List<string>();
                DataUniversal.ExecuteQueryCoreWhile(string.Format(PragmaTableInfoTemplate, tableName),
                    r => cols.Add(r.GetString(1)));
                return cols;
            }

            static public string GetColumnValueAsString(SQLiteDataReader reader, string columnName) =>
                reader[columnName]?.ToString() ?? string.Empty;

            static public List<List<string>> ReadCompositeData(string tableName, List<string> columnNames)
            {
                var res = new List<List<string>>();
                if (columnNames?.Count > 0)
                {
                    string query = string.Format(SelectColumnsFromTableTemplate, string.Join(", ", columnNames), tableName);
                    DataUniversal.ExecuteQueryCoreWhile(query, r =>
                    {
                        var row = new List<string>();
                        foreach (string col in columnNames) row.Add(GetColumnValueAsString(r, col));
                        res.Add(row);
                    });
                }
                return res;
            }

            public static List<string> GetRowValue(string tableName, string rowName, string valueToMatch)
            {
                var res = new List<string>();
                DataUniversal.QueryRow(string.Format(SelectAllFromTableWhereTemplate, tableName, rowName), valueToMatch, r =>
                {
                    for (int i = 0; i < r.FieldCount; i++) res.Add(r.GetValue(i).ToString());
                });
                if (res.Count == 0) throw new Exception(string.Format(NoDataFoundMessage, tableName, rowName, valueToMatch));
                return res;
            }

            public static string GetRowColumnValue(string tableName, string rowName, string valueToMatch, string columnName)
            {
                string val = null;
                DataUniversal.QueryRow(string.Format(SelectColumnFromTableWhereTemplate, columnName, tableName, rowName), valueToMatch,
                    r => val = r.IsDBNull(0) ? null : r.GetValue(0)?.ToString());
                return val;
            }

            static public List<string> SQLiteGetTableNames()
            {
                var names = new List<string>();
                DataUniversal.ExecuteQueryCoreWhile(SelectAllTablesQuery,
                    r => names.Add(r.GetString(0)));
                return names;
            }

            static public List<string> ReadColumn(string tableName, string columnName)
            {
                var data = new List<string>();
                DataUniversal.ExecuteQueryCoreWhile(string.Format(SelectColumnsFromTableTemplate, columnName, tableName),
                    r => data.Add(r[columnName].ToString()));
                return data;
            }
        }

        static public class DataUpdate
        {
            // 常量定义
            private const string CreateTableTemplate = "CREATE TABLE {0} ({1})";
            private const string ColumnDefinitionTemplate = "{0} TEXT";
            private const string InsertIntoTemplate = "INSERT INTO {0} SELECT * FROM {1}";
            private const string UpdateTableSetWhereTemplate = "UPDATE {0} SET {1} WHERE RowName = @targetRowName";
            private const string RenameColumnTemplate = "ALTER TABLE {0} RENAME COLUMN {1} TO {2};";
            private const string AddColumnTemplate = "ALTER TABLE {0} ADD COLUMN {1};";
            private const string CreateTempTableTemplate = "CREATE TABLE {0} ({1});";
            private const string DropTableTemplate = "DROP TABLE {0};";
            private const string RenameTableTemplate = "ALTER TABLE {0} RENAME TO {1};";
            private const string InsertIntoTempSelectFromSourceTemplate = "INSERT INTO {0} ({1}) SELECT {2} FROM {3};";
            private const string InsertIntoTempSelectFromSourceWhereTemplate = "INSERT INTO {0} ({1}) SELECT {2} FROM {3} WHERE {4} = @rowKey;";
            private const string NoColumnsFoundMessage = "在表 '{0}' 中未能找到任何列。";
            private const string TableNameEmptyMessage = "表名不能为空或仅包含空白字符。";
            private const string ColumnOrderEmptyMessage = "列顺序列表不能为空。";
            private const string RowOrderEmptyMessage = "行顺序列表不能为空。";
            private const string CannotGetFirstColumnMessage = "无法获取表 '{0}' 的第一列名。";
            private const string SourceTableEmptyMessage = "源表名不能为空。";
            private const string TargetTableEmptyMessage = "目标表名不能为空。";
            private const string ColumnNameEmptyMessage = "列名不能为空。";
            private const string UpdateFailedMessage = "更新失败: 表 {0} 中未找到 RowName={1} 的行";
            private const string ColumnDefinitionEmptyMessage = "列定义不能为空";

            public static void CopyTable(string sourceTable, string targetTable)
            {
                var srcCols = DataRead.GetTableColumns(sourceTable);
                if (srcCols == null || srcCols.Count == 0) throw new Exception($"未找到表 {sourceTable} 的结构定义。");
                string colDefs = string.Join(", ", srcCols.Select(c => string.Format(ColumnDefinitionTemplate, c)));
                string createSql = string.Format(CreateTableTemplate, targetTable, colDefs);
                DataCreate.CreateTable(targetTable, () => createSql);
                DataUniversal.ExecuteNonQuery(string.Format(InsertIntoTemplate, targetTable, sourceTable));
            }

            public static int UpdateExistingRowByName(string tableName, string targetRowName, Dictionary<string, object> parameters)
            {
                string set = string.Join(", ", parameters.Select(p => $"{p.Key} = @{p.Key}"));
                var allParams = new Dictionary<string, object>(parameters) { { "@targetRowName", targetRowName } };
                int rows = 0;
                DataUniversal.ExecuteSQL(string.Format(UpdateTableSetWhereTemplate, tableName, set), allParams,
                    cmd => rows = cmd.ExecuteNonQuery());
                if (rows == 0) throw new Exception(string.Format(UpdateFailedMessage, tableName, targetRowName));
                return rows;
            }

            public static void RenameColumn(string tableName, string oldColumnName, string newColumnName) =>
                DataUniversal.ExecuteNonQuery(string.Format(RenameColumnTemplate, tableName, oldColumnName, newColumnName));

            public static void AddColumn(string tableName, string columnDefinition)
            {
                if (string.IsNullOrWhiteSpace(columnDefinition)) throw new ArgumentException(ColumnDefinitionEmptyMessage);
                DataUniversal.ExecuteNonQuery(string.Format(AddColumnTemplate, tableName, columnDefinition));
            }

            private static void CreateTempTableAndReplace(string tableName, List<string> columnDefs, Action<string> insertAction)
            {
                string tmp = tableName + "_tmp";
                DataUniversal.ExecuteNonQuery(string.Format(CreateTempTableTemplate, tmp, string.Join(", ", columnDefs)));
                insertAction(tmp);
                DataUniversal.ExecuteNonQuery(string.Format(DropTableTemplate, tableName));
                DataUniversal.ExecuteNonQuery(string.Format(RenameTableTemplate, tmp, tableName));
            }

            public static void ReorderColumns(string tableName, List<string> newColumnOrder)
            {
                if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentException(TableNameEmptyMessage, nameof(tableName));
                if (newColumnOrder == null || newColumnOrder.Count == 0) throw new ArgumentException(ColumnOrderEmptyMessage, nameof(newColumnOrder));
                var columns = DataRead.GetTableColumns(tableName);
                if (columns == null || !columns.Any()) throw new InvalidOperationException(string.Format(NoColumnsFoundMessage, tableName));
                var allDefs = DataUniversal.GetTableColumnDefinitions(tableName);
                var dict = allDefs.ToDictionary(def => def.Split(' ')[0], def => def);
                var newDefs = newColumnOrder.Where(columns.Contains).Select(col => dict[col]).ToList();
                CreateTempTableAndReplace(tableName, newDefs, tmp =>
                {
                    string insertSql = string.Format(InsertIntoTempSelectFromSourceTemplate, tmp, string.Join(", ", newColumnOrder), string.Join(", ", newColumnOrder), tableName);
                    DataUniversal.ExecuteNonQuery(insertSql);
                });
            }

            public static void ReorderRows(string tableName, List<string> newRowOrder)
            {
                if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentException(TableNameEmptyMessage, nameof(tableName));
                if (newRowOrder == null || newRowOrder.Count == 0) throw new ArgumentException(RowOrderEmptyMessage, nameof(newRowOrder));
                var columns = DataRead.GetTableColumns(tableName);
                if (columns == null || !columns.Any()) throw new InvalidOperationException(string.Format(NoColumnsFoundMessage, tableName));
                string rowName = DataSave.GetFirstColumnName(tableName);
                if (string.IsNullOrWhiteSpace(rowName)) throw new InvalidOperationException(string.Format(CannotGetFirstColumnMessage, tableName));
                var columnDefs = DataUniversal.GetTableColumnDefinitions(tableName);
                CreateTempTableAndReplace(tableName, columnDefs, tmp =>
                {
                    foreach (var key in newRowOrder)
                    {
                        string insertSql = string.Format(InsertIntoTempSelectFromSourceWhereTemplate, tmp, string.Join(", ", columns), string.Join(", ", columns), tableName, rowName);
                        DataUniversal.ExecuteNonQuery(insertSql, new Dictionary<string, object> { { "@rowKey", key } });
                    }
                });
            }
        }

        static public class MutipleData
        {
            static public class MutipleDataRead
            {
                // 常量定义
                private const string SelectAllFromTableWhereTemplate = "SELECT * FROM [{0}] WHERE [{1}] = @value";

                static public List<Dictionary<string, object>> ReadRowsByColumnValue(string tableName, string columnName, object value)
                {
                    var res = new List<Dictionary<string, object>>();
                    DataUniversal.ExecuteQueryCoreWhile(string.Format(SelectAllFromTableWhereTemplate, tableName, columnName),
                        r =>
                        {
                            var row = new Dictionary<string, object>();
                            for (int i = 0; i < r.FieldCount; i++) row[r.GetName(i)] = r.GetValue(i);
                            res.Add(row);
                        }, new Dictionary<string, object> { { "@value", value ?? DBNull.Value } });
                    return res;
                }
            }

            static public class MutipleDataUpdate
            {
                // 常量定义
                private const string InsertIntoTemplate = "INSERT INTO [{0}] ({1}) VALUES ({2})";
                private const string DeleteFromWhereTemplate = "DELETE FROM [{0}] WHERE [{1}] = @value";
                private const string SourceTableEmptyMessage = "源表名不能为空。";
                private const string TargetTableEmptyMessage = "目标表名不能为空。";
                private const string ColumnNameEmptyMessage = "列名不能为空。";

                static public void MoveRowsByColumnValue(string sourceTableName, string targetTableName, string columnName, object value)
                {
                    if (string.IsNullOrWhiteSpace(sourceTableName)) throw new ArgumentException(SourceTableEmptyMessage, nameof(sourceTableName));
                    if (string.IsNullOrWhiteSpace(targetTableName)) throw new ArgumentException(TargetTableEmptyMessage, nameof(targetTableName));
                    if (string.IsNullOrWhiteSpace(columnName)) throw new ArgumentException(ColumnNameEmptyMessage, nameof(columnName));

                    var rows = MutipleDataRead.ReadRowsByColumnValue(sourceTableName, columnName, value);
                    if (rows.Count == 0) return;

                    foreach (var row in rows)
                    {
                        var cols = string.Join(", ", row.Keys.Select(k => $"[{k}]"));
                        var vals = string.Join(", ", row.Keys.Select(k => $"@{k}"));
                        DataUniversal.ExecuteSQL(string.Format(InsertIntoTemplate, targetTableName, cols, vals),
                            row.ToDictionary(k => $"@{k.Key}", k => k.Value ?? DBNull.Value),
                            cmd => cmd.ExecuteNonQuery());
                    }

                    DataUniversal.ExecuteNonQuery(string.Format(DeleteFromWhereTemplate, sourceTableName, columnName),
                        new Dictionary<string, object> { { "@value", value ?? DBNull.Value } });
                }
            }
        }
    }



    public static class AutoStorage
    {
        private const string IdColumn = "Id";
        private const string IdColumnDef = "[Id] INTEGER PRIMARY KEY AUTOINCREMENT";
        private const string RowNameColumn = "RowName";

        private static void EnsureTableInternal(string tableName, IEnumerable<string> cols = null)
        {
            if (DataCreate.DoesTableExist(tableName)) return;
            var defs = new List<string> { IdColumnDef };
            if (cols != null)
                defs.AddRange(cols.Where(c => !string.Equals(c, IdColumn, StringComparison.OrdinalIgnoreCase))
                                  .Select(c => $"[{c}] TEXT"));
            DataUniversal.ExecuteNonQuery($"CREATE TABLE [{tableName}] ({string.Join(", ", defs)})");
        }

        public static void EnsureTable(string tableName) => EnsureTableInternal(tableName, new[] { "Content" });

        public static void AddMissingColumns(string tableName, IEnumerable<string> columnNames)
        {
            var existing = DataRead.GetTableColumns(tableName);
            foreach (var col in columnNames.Where(c => !string.Equals(c, IdColumn, StringComparison.OrdinalIgnoreCase) &&
                                                       !existing.Contains(c, StringComparer.OrdinalIgnoreCase)))
                DataUpdate.AddColumn(tableName, $"[{col}] TEXT");
        }

        public static int SaveData(string tableName, Dictionary<string, object> data)
        {
            if (data == null) throw new ArgumentNullException(nameof(data));

            var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
            data[RowNameColumn] = timestamp;

            var dataCopy = new Dictionary<string, object>(data);
            dataCopy.Remove(IdColumn);
            if (dataCopy.Count == 0) return 0;

            EnsureTableInternal(tableName, dataCopy.Keys);
            AddMissingColumns(tableName, dataCopy.Keys);

            var cols = dataCopy.Keys.ToList();
            var parameters = dataCopy.ToDictionary(kv => "@" + kv.Key, kv => kv.Value ?? DBNull.Value);
            string sql = $"INSERT INTO [{tableName}] ({string.Join(", ", cols.Select(c => $"[{c}]"))}) " +
                         $"VALUES ({string.Join(", ", cols.Select(c => "@" + c))})";
            int rows = 0;
            DataUniversal.ExecuteSQL(sql, parameters, cmd => rows = cmd.ExecuteNonQuery());
            return rows;
        }

        public static void SaveDataBatch(string tableName, IEnumerable<Dictionary<string, object>> dataList)
        {
            foreach (var data in dataList) SaveData(tableName, data);
        }

        public static List<Dictionary<string, object>> LoadAllData(string tableName)
        {
            var result = new List<Dictionary<string, object>>();
            DataUniversal.ExecuteQueryCoreWhile($"SELECT * FROM [{tableName}]", r =>
            {
                var row = new Dictionary<string, object>();
                for (int i = 0; i < r.FieldCount; i++) row[r.GetName(i)] = r.GetValue(i);
                result.Add(row);
            });
            return result;
        }

        // 保存单个字符串(向下兼容)
        public static void SaveString(string tableName, string content) =>
            SaveData(tableName, new Dictionary<string, object> { { "Content", content } });

        // 保存字符串数组(每个元素存为独立的列 Content1, Content2...)
        public static void SaveStrings(string tableName, params string[] contents) =>
            SaveStrings(tableName, contents.ToList());

        // 保存字符串列表(每个元素存为独立的列 Content1, Content2...)
        public static void SaveStrings(string tableName, List<string> contents)
        {
            if (contents == null) throw new ArgumentNullException(nameof(contents));
            var data = new Dictionary<string, object>();
            for (int i = 0; i < contents.Count; i++)
            {
                string colName = i == 0 ? "Content" : $"Content{i + 1}"; // 第一项存为 Content,后续为 Content2, Content3...
                data[colName] = contents[i];
            }
            SaveData(tableName, data);
        }

        // 加载所有字符串(兼容新旧格式)
        public static List<string> LoadAllStrings(string tableName)
        {
            var result = new List<string>();
            var allRows = LoadAllData(tableName);
            if (allRows.Count == 0) return result;

            // 获取所有列名,找出以 "Content" 开头的列
            var contentColumns = DataRead.GetTableColumns(tableName)
                                         .Where(col => col.StartsWith("Content", StringComparison.OrdinalIgnoreCase))
                                         .ToList();

            foreach (var row in allRows)
            {
                foreach (var col in contentColumns)
                {
                    if (row.TryGetValue(col, out var val) && val != null && val != DBNull.Value)
                        result.Add(val.ToString());
                }
            }
            return result;
        }
    }


    #endregion
}
相关推荐
HY小海2 小时前
【Unity游戏创作】常见的设计模式
unity·设计模式·c#·游戏程序
专注VB编程开发20年2 小时前
C#,VB.NET如何用GPU进行大量计算,提高效率?
开发语言·c#·.net
喵手2 小时前
Python爬虫实战:Boss直聘职位数据采集实战 - Playwright + 结构化解析完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·sqlite·爬虫实战·playwright·boss直聘职位数据采集·结构化解析
qq_454245033 小时前
开源GraphMindStudio工作流引擎:自动化与AI智能体的理想核心
运维·人工智能·开源·c#·自动化
(initial)17 小时前
B-02. Shared Memory 深度优化:从 Bank Conflict 到 Tensor Core Swizzling
开发语言·c#
fdc20171 天前
解耦的艺术:用责任链模式构建可插拔的文件处理流水线
c#·.net·责任链模式
bugcome_com1 天前
【C# 数组详解】Array 定义、初始化、遍历、内存原理与面试高频问题
后端·c#·asp.net
小码编匠1 天前
WPF 如何在 MVVM模式下实现 DataGrid编辑功能
后端·c#·.net
游乐码1 天前
c#扩展方法
开发语言·c#