#unity/代码缓存 配合[[队列操作]]管理好,异步并发的启动顺序 ## SQLiteManager ``` cs using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using System.IO; using System.Linq.Expressions; using System.Threading; using CGBU_SDK; namespace SQLite4Unity3d { public class TaskQueue { private ConcurrentQueue> _tasks = new ConcurrentQueue>(); private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // 用于无返回值的任务 public Task EnqueueTask(Func task) { var tcs = new TaskCompletionSource(); _tasks.Enqueue(async () => { try { await task(); tcs.SetResult(null); } catch (Exception ex) { tcs.SetException(ex); } }); TryExecuteNextTask(); return tcs.Task; } // 用于有返回值的任务 public Task EnqueueTask(Func> task) { var tcs = new TaskCompletionSource(); _tasks.Enqueue(async () => { try { T result = await task(); tcs.SetResult(result); } catch (Exception ex) { tcs.SetException(ex); } }); TryExecuteNextTask(); return tcs.Task; } private async void TryExecuteNextTask() { if (_semaphore.CurrentCount == 0) return; if (_tasks.TryDequeue(out var nextTask)) { await _semaphore.WaitAsync(); try { await nextTask(); } finally { _semaphore.Release(); TryExecuteNextTask(); } } } } public class SQLiteManager { private SQLiteConnection _db; //数据库连接 /// /// 数据库 名称 ,数据库 多个表的类型 /// /// /// public SQLiteManager(string dbName, params Type[] tableTypes) { CreateSQLiteDataBase($"{dbName}.db"); // 确保所有传入的表类型都存在 foreach (var type in tableTypes) { EnsureTableExists(type); } } /// /// 插入 特定表 单条数据 /// /// /// public async Task InsertDataAsync(T data) where T : new() { await Task.Run(() => { _db.RunInTransaction(() => { try { _db.Insert(data); } catch (Exception ex) { Debug.LogError($"Error inserting {typeof(T).Name}: {ex.Message}"); throw; } }); }); } /// /// 批量插入 特定表 数据 /// /// /// public async Task InsertBatchDataAsync(IEnumerable dataList) where T : new() { await Task.Run(() => { _db.RunInTransaction(() => { try { _db.InsertAll(dataList); } catch (Exception ex) { Debug.LogError($"Error inserting batch data for {typeof(T).Name}: {ex.Message}"); throw; } }); }); } /// /// 获取 特定表中 所有数据 /// /// /// public async Task> GetAllDataAsync() where T : new() { return await Task.Run(() => { lock (_db) // 确保线程安全 { try { // 获取数据并转换为 object 类型 return _db.Table().ToList().Cast().ToList(); } catch (Exception ex) { Debug.LogError($"Error fetching data from {typeof(T).Name}: {ex.Message}"); return new List(); // 返回空列表以避免错误 } } }); } /// /// 删除 特定表中 所有数据 (保留表结构) /// /// public async Task DeleteAllDataAsync() where T : new() { await Task.Run(() => { lock (_db) // 线程安全 { _db.RunInTransaction(() => { try { string tableName = typeof(T).Name; _db.Execute($"DELETE FROM {tableName}"); } catch (Exception ex) { Debug.LogError($"Error deleting all records from {typeof(T).Name}: {ex.Message}"); throw; } }); } }); } /// /// 获得表里 最新的数据 /// /// /// public async Task GetMostRecentDataAsync() where T : new() { return await Task.Run(() => { lock (_db) // Ensures thread safety { try { // Query to get the most recent row based on the 'id' field. string query = $"SELECT * FROM {typeof(T).Name} ORDER BY id DESC LIMIT 1"; var result = _db.Query(query).FirstOrDefault(); return result; } catch (Exception ex) { Debug.LogError($"Error fetching the most recent data from {typeof(T).Name}: {ex.Message}"); return default(T); // Return default object if error occurs } } }); } /// /// 根据uuid获取数据库中 特定表数据 /// /// /// /// public async Task GetDataByIdAsync(string uuid) where T : new() { return await Task.Run(() => { try { return _db.Table().FirstOrDefault(e => (string)e.GetType().GetProperty("uuid").GetValue(e) == uuid); } catch (Exception ex) { Debug.LogError($"Error retrieving data from {typeof(T).Name}: {ex.Message}"); return default; // 返回 null(适用于引用类型) } }); } public async Task UpdateDataByUuidAsync(string uuid, T newData) where T : new() { await Task.Run(() => { try { // Check if the entry exists var existingData = _db.Table().FirstOrDefault(e => (string)e.GetType().GetProperty("uuid").GetValue(e) == uuid); if (existingData != null) { // **Update the fields of existing data with new data** foreach (var prop in typeof(T).GetProperties()) { // Skipping the uuid property to avoid overwriting it if (prop.Name != "uuid") { var newValue = prop.GetValue(newData); prop.SetValue(existingData, newValue); } } // **Update the record in the database** _db.Update(existingData); // Updates the record } else { Debug.LogWarning($"No record found with uuid: {uuid}"); } } catch (Exception ex) { Debug.LogError($"Error updating data in {typeof(T).Name}: {ex.Message}"); } }); } /// /// 传参uuids列表,不在uuids列表中的数据库数据,移除 /// /// /// /// public async Task DeleteExceptUuidsAsync(List uuids, string columnName = "uuid") where T : new() { if (uuids == null || uuids.Count == 0) { Debug.LogWarning("DeleteExceptUuidsAsync: UUID list is empty. No data will be deleted."); return; } await Task.Run(() => { lock (_db) // 线程安全 { _db.RunInTransaction(() => { try { // 生成SQL:删除所有不在 UUID 列表中的数据 string placeholders = string.Join(",", uuids.Select(_ => "?")); string sql = $"DELETE FROM {typeof(T).Name} WHERE {columnName} NOT IN ({placeholders})"; _db.Execute(sql, uuids.ToArray()); } catch (Exception ex) { Debug.LogError($"Error deleting records not in the UUID list from {typeof(T).Name}: {ex.Message}"); throw; } }); } }); } /// /// 批量更新 多个表 数据 /// /// /// /// public async Task UpsertDataAsync(Dictionary> tableDataMap, string columnName = "uuid") { if (tableDataMap == null || tableDataMap.Count == 0) { Debug.LogWarning("UpsertDataAsync: No data provided for any table. No operation performed."); return; } await Task.Run(() => { lock (_db) // 线程安全 { _db.RunInTransaction(() => { try { foreach (var entry in tableDataMap) { Type tableType = entry.Key; List dataList = entry.Value; if (dataList == null || dataList.Count == 0) { Debug.LogWarning($"UpsertDataAsync: No data provided for {tableType.Name}. Skipping..."); try { // Clear the table by executing a DELETE statement string clearTable = $"DELETE FROM {tableType.Name}"; _db.Execute(clearTable); } catch (Exception ex) { Debug.LogError($"Error clearing data in {tableType.Name}: {ex.Message}"); } continue; } var propertyInfo = tableType.GetProperty(columnName); if (propertyInfo == null) { throw new ArgumentException($"Column '{columnName}' not found in {tableType.Name}."); } // **1️⃣ 获取数据库已有的 UUIDs** string query = $"SELECT {columnName} FROM {tableType.Name}"; var tableMapping = _db.GetMapping(tableType); var existingUuids = new HashSet( _db.Query(tableMapping, query) .Select(d => propertyInfo.GetValue(d)?.ToString()) .Where(uuid => !string.IsNullOrEmpty(uuid)) ); // **2️⃣ 删除数据库中不在 `incomingUuids` 里的数据** var incomingUuids = new HashSet( dataList.Select(d => propertyInfo.GetValue(d)?.ToString()) .Where(uuid => !string.IsNullOrEmpty(uuid)) ); if (incomingUuids.Count == 0) { Debug.LogWarning($"UpsertDataAsync: No valid UUIDs found in the data list for {tableType.Name}."); continue; } string placeholders = string.Join(",", incomingUuids.Select(_ => "?")); string deleteSql = $"DELETE FROM {tableType.Name} WHERE {columnName} NOT IN ({placeholders})"; _db.Execute(deleteSql, incomingUuids.ToArray()); // **3️⃣ 计算需要更新和插入的数据** var toUpdateList = dataList.Where(d => { var uuid = propertyInfo.GetValue(d)?.ToString(); return !string.IsNullOrEmpty(uuid) && existingUuids.Contains(uuid); }).ToList(); var toInsertList = dataList.Where(d => { var uuid = propertyInfo.GetValue(d)?.ToString(); return !string.IsNullOrEmpty(uuid) && !existingUuids.Contains(uuid); }).ToList(); // **4️⃣ 更新已有数据** foreach (var item in toUpdateList) { _db.Update(item); } // **5️⃣ 插入新数据** if (toInsertList.Count > 0) { _db.InsertAll(toInsertList); } } } catch (Exception ex) { Debug.LogError($"Error in UpsertDataAsync: {ex.Message}"); throw; } }); } }); } public void UpsertData(Dictionary> tableDataMap, string columnName = "uuid") { if (tableDataMap == null || tableDataMap.Count == 0) { Debug.LogWarning("UpsertData: No data provided for any table. No operation performed."); return; } lock (_db) // 线程安全 { _db.RunInTransaction(() => { try { foreach (var entry in tableDataMap) { Type tableType = entry.Key; List dataList = entry.Value; if (dataList == null || dataList.Count == 0) { Debug.LogWarning($"UpsertData: No data provided for {tableType.Name}. Skipping..."); try { // Clear the table by executing a DELETE statement string clearTable = $"DELETE FROM {tableType.Name}"; _db.Execute(clearTable); } catch (Exception ex) { Debug.LogError($"Error clearing data in {tableType.Name}: {ex.Message}"); } continue; } var propertyInfo = tableType.GetProperty(columnName); if (propertyInfo == null) { throw new ArgumentException($"Column '{columnName}' not found in {tableType.Name}."); } // 获取数据库已有的 UUIDs string query = $"SELECT {columnName} FROM {tableType.Name}"; var tableMapping = _db.GetMapping(tableType); var existingUuids = new HashSet( _db.Query(tableMapping, query) .Select(d => propertyInfo.GetValue(d)?.ToString()) .Where(uuid => !string.IsNullOrEmpty(uuid)) ); // 删除数据库中不在 `incomingUuids` 里的数据 var incomingUuids = new HashSet( dataList.Select(d => propertyInfo.GetValue(d)?.ToString()) .Where(uuid => !string.IsNullOrEmpty(uuid)) ); if (incomingUuids.Count == 0) { Debug.LogWarning($"UpsertData: No valid UUIDs found in the data list for {tableType.Name}."); continue; } string placeholders = string.Join(",", incomingUuids.Select(_ => "?")); string deleteSql = $"DELETE FROM {tableType.Name} WHERE {columnName} NOT IN ({placeholders})"; _db.Execute(deleteSql, incomingUuids.ToArray()); // 计算需要更新和插入的数据 var toUpdateList = dataList.Where(d => { var uuid = propertyInfo.GetValue(d)?.ToString(); return !string.IsNullOrEmpty(uuid) && existingUuids.Contains(uuid); }).ToList(); var toInsertList = dataList.Where(d => { var uuid = propertyInfo.GetValue(d)?.ToString(); return !string.IsNullOrEmpty(uuid) && !existingUuids.Contains(uuid); }).ToList(); // 更新已有数据 foreach (var item in toUpdateList) { _db.Update(item); } // 插入新数据 if (toInsertList.Count > 0) { _db.InsertAll(toInsertList); } } } catch (Exception ex) { Debug.LogError($"Error in UpsertData: {ex.Message}"); throw; } }); } } /// /// 清楚替换,单个表结构 /// /// /// public async Task BulkReplaceDataAsync(List newDataList) where T : new() { if (newDataList == null || newDataList.Count == 0) { Debug.LogWarning($"BulkReplaceDataAsync: No data provided for table {typeof(T).Name}. Operation aborted."); return; } await Task.Run(() => { lock (_db) // 确保线程安全 { _db.RunInTransaction(() => { try { _db.DeleteAll(); // 清空表数据 _db.InsertAll(newDataList); // 批量插入新的数据 Debug.Log($"BulkReplaceDataAsync: Successfully replaced data in {typeof(T).Name}."); } catch (Exception ex) { Debug.LogError($"BulkReplaceDataAsync: Error processing table {typeof(T).Name}: {ex.Message}"); } }); } }); } public async Task BulkReplaceMultipleTablesAsync(params (Type TableType, IList Data)[] tablesData) { if (tablesData == null || tablesData.Length == 0) { Debug.LogWarning("BulkReplaceMultipleTablesAsync: No data provided, no operation performed."); return; } await Task.Run(() => { lock (_db) // 确保数据库线程安全 { _db.RunInTransaction(() => { try { foreach (var (tableType, data) in tablesData) { if (data == null || data.Count == 0) continue; // 清空表数据 string tableName = tableType.Name; _db.Execute($"DELETE FROM {tableName}"); // 批量插入数据 _db.InsertAll(data); Debug.Log($"BulkReplaceMultipleTablesAsync: Replaced data for {tableName}"); } } catch (Exception ex) { Debug.LogError($"BulkReplaceMultipleTablesAsync: Error processing tables: {ex.Message}"); throw; } }); } }); } /// /// 获取 | 创建 SQLite数据库文件 /// /// private void CreateSQLiteDataBase(string DatabaseName) { #if UNITY_EDITOR var dbPath = string.Format(@"Assets/StreamingAssets/{0}", DatabaseName); #else // check if file exists in Application.persistentDataPath var filepath = string.Format("{0}/{1}", Application.persistentDataPath, DatabaseName); if (!File.Exists(filepath)) { Debug.Log("Database not in Persistent path"); // if it doesn't -> // open StreamingAssets directory and load the db -> #if UNITY_ANDROID var loadDb = new WWW("jar:file://" + Application.dataPath + "!/assets/" + DatabaseName); // this is the path to your StreamingAssets in android while (!loadDb.isDone) { } // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check // then save to Application.persistentDataPath File.WriteAllBytes(filepath, loadDb.bytes); #elif UNITY_IOS var loadDb = Application.dataPath + "/Raw/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); #elif UNITY_WP8 var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); #elif UNITY_WINRT var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); #elif UNITY_STANDALONE_OSX var loadDb = Application.dataPath + "/Resources/Data/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); #else var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); #endif Debug.Log("Database written"); } var dbPath = filepath; #endif _db = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create); _db.Execute("PRAGMA synchronous=NORMAL;"); // string sqliteVersion = _db.ExecuteScalar("SELECT sqlite_version();"); // Debug.Log($"SQLite 版本: {sqliteVersion}"); // _db.Execute("PRAGMA journal_mode=WAL;"); // 启用 WAL 模式 // string mode = _db.ExecuteScalar("PRAGMA journal_mode=WAL;"); // Debug.Log($"WAL 模式启用: {mode}"); // // if (mode != "wal") // { // Debug.LogError("WAL 模式未成功启用,请检查数据库权限或文件占用情况!"); // } // _db.DropTable (); // _db.CreateTable (); // Debug.Log("数据库文件 所在路径:" + dbPath); } /// /// 判断数据库中 表是否存在 /// /// public void EnsureTableExists(Type tableType) { string tableName = tableType.Name; // 查询 sqlite_master 来检查表是否存在 var tableExists = _db.ExecuteScalar($"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='{tableName}';"); if (tableExists == 0) // 如果表不存在 { _db.CreateTable(tableType); // 创建表 Debug.Log($"Table {tableName} created."); } else { Debug.Log($"Table {tableName} already exists, skipping creation."); } } } } ``` ## 数据结构参考: ``` cs /// /// 开始&结束会话 /// [Serializable] public class SessionEventData { public string uuid; public string event_name; public long event_timestamp; public string public_properties; public Dictionary event_properties; public int snum; //发送计数器 } /// /// 普通会话 /// [Serializable] public class EventData { public string uuid; public string event_name; public long event_timestamp; public string public_properties; public Dictionary event_properties; public int snum; //发送计数器 } /// /// 事件类型 /// public enum SessionType { start_end_session, //开始/结束会话 default_session //普通会话 } /// /// 开始&结束会话 SQLite存储的消息结构 /// public class SQLiteSessionEventData { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string uuid{ get; set; } public string event_name { get; set; } public long event_timestamp { get; set; } public string public_properties { get; set; } public string event_properties { get; set; } public int snum { get; set; } // 发送计数器 } /// /// 普通会话 SQLite存储的消息结构 /// public class SQLiteEventData { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string uuid{ get; set; } public string event_name { get; set; } public long event_timestamp { get; set; } public string public_properties { get; set; } public string event_properties { get; set; } public int snum { get; set; } // 发送计数器 } /// /// 临时ID SQLite存储结构 /// public class SQLiteTempIdData { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string TempId { get; set; } public string uuid { get; set; } public string RealId { get; set; } } ``` ## 调用参考: ``` cs public async void UpdateSQLiteCache() { if (TimeTickUtil.GetEventTimesTamp() - LastChangeDataTime >= ChangeDataTime && isInitData) { LastChangeDataTime = TimeTickUtil.GetEventTimesTamp(); List SQLiteSessionEventDataList = new List(); //数据库中 SessionEventData 表数据 (开始&结束会话) List SQLiteEventDataList = new List(); //数据库中 EventData 表数据 (普通事件) foreach (KeyValuePair kvp in SessionDic[SessionType.start_end_session]) { SessionEventData sessionEventData = kvp.Value as SessionEventData; string sessionEventJosn = JsonConvert.SerializeObject(sessionEventData.event_properties); var insertSessionData = new SQLiteSessionEventData { uuid = sessionEventData.uuid, event_name = sessionEventData.event_name, event_timestamp = sessionEventData.event_timestamp, public_properties = sessionEventData.public_properties, event_properties = sessionEventJosn, snum = sessionEventData.snum }; SQLiteSessionEventDataList.Add(insertSessionData); } foreach (KeyValuePair kvp in SessionDic[SessionType.default_session]) { EventData eventData = kvp.Value as EventData; string sessionEventJosn = JsonConvert.SerializeObject(eventData.event_properties); var insertSessionData = new SQLiteEventData { uuid = eventData.uuid, event_name = eventData.event_name, event_timestamp = eventData.event_timestamp, public_properties = eventData.public_properties, event_properties = sessionEventJosn, snum = eventData.snum }; SQLiteEventDataList.Add(insertSessionData); } await taskQueue.EnqueueTask(() => SQLiteManager.UpsertDataAsync(new Dictionary> { { typeof(SQLiteSessionEventData), SQLiteSessionEventDataList }, { typeof(SQLiteEventData), SQLiteEventDataList }, })); Debug.Log("数据更新完成"); } } ```