在 Unity 项目开发中,经常需要与本地数据库(如 SQLite)进行交互。直接使用 System.Data.SQLite 的原始 API 会导致大量重复代码,且容易出错。GraphMindStudio.Data 命名空间下提供了一套封装完善的静态类库,旨在简化 SQLite 的常用操作,并提供自动化存储机制。本文将深入剖析该库的设计思路、核心功能及使用方法。
一、整体架构
该库主要包含两大静态类:
DataBase:核心数据库操作类,内部嵌套了多个功能模块,分别负责连接管理、表结构维护、数据增删改查、多表协同等。
AutoStorage:基于 DataBase 实现的自动化存储工具,支持动态列扩展、批量保存和灵活加载,特别适合存储结构不固定的数据。
所有类均使用静态成员,无需实例化,直接通过类名调用方法,符合工具类设计习惯。
二、数据库连接与基础操作(DataUniversal)
DataUniversal 是所有数据库操作的基石,负责建立连接、执行 SQL 并处理异常。
- 连接管理
SetConnect():根据预设的数据库路径(默认位于 StreamingAssets/DataBase/MainDataBase.db)创建并打开 SQLiteConnection。路径可自定义修改 DataBasePath 静态字段。
连接字符串使用 "Data Source=...;Version=3;" 格式,确保兼容性。
- 核心执行方法
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 不仅支持数据更新,还提供了表结构变更的辅助方法。
-
数据更新
UpdateExistingRowByName:按 RowName 列更新指定行,参数为列名-新值的字典。
-
表结构变更
CopyTable:复制源表结构及数据到新表(自动创建目标表)。
RenameColumn / AddColumn:调用 SQLite 的 ALTER TABLE 实现列重命名和添加。
ReorderColumns:重新排序列的顺序。实现方式:创建临时表,按新顺序定义列,将原数据按新列顺序插入,再替换原表。
ReorderRows:按指定的第一列值顺序重新排序行。同样采用临时表策略,遍历新顺序依次插入。
这些结构修改操作在 SQLite 原生不支持直接修改列顺序的背景下,通过临时表迁移实现了"伪重排"。
七、多表操作(MutipleData)
MutipleData 内部嵌套了 MutipleDataRead 和 MutipleDataUpdate,用于处理涉及多表的批量操作。
ReadRowsByColumnValue:读取指定表中满足某列值的所有行,每行以 Dictionary<string, object> 返回(列名→值)。
MoveRowsByColumnValue:将源表中满足条件的所有行移动到目标表。过程为先读取所有匹配行,逐行插入目标表,最后从源表删除这些行。该操作在单个事务中执行,保证原子性(但代码中未显式开启事务,需注意)。
八、自动化存储(AutoStorage)
AutoStorage 是一个高层次的封装,旨在让开发者无需关心表结构即可存储任意键值对数据。
- 表结构约定
每个表默认包含 Id INTEGER PRIMARY KEY AUTOINCREMENT 作为自增主键。
额外列动态创建,列名由传入数据的键决定。
预留 RowName 列用于存储时间戳(记录插入时间)。
- 核心方法
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
}