Net.Like.Xue.Tokyo/Assets/BITKit/LiteDb/LiteDbDictionary.cs

238 lines
7.7 KiB
C#
Raw Normal View History

2025-04-17 19:36:08 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
2025-06-09 15:55:24 +08:00
using System.IO;
2025-04-17 19:36:08 +08:00
using System.Linq;
2025-06-09 15:55:24 +08:00
using System.Reflection;
using System.Text.RegularExpressions;
2025-04-17 19:36:08 +08:00
using LiteDB;
2025-06-09 15:55:24 +08:00
using Microsoft.Extensions.Logging;
2025-04-17 19:36:08 +08:00
namespace Net.BITKit.Database
2025-06-09 15:55:24 +08:00
{public static class LiteDbHelper
{
public static string GetReadableCollectionName<T>()
{
var type = typeof(T);
return SanitizeTypeName(type);
}
private static string SanitizeTypeName(Type type)
{
// 基础类型名
string name = type.Name;
// 若为泛型,如 Dictionary<string, int> 变为 Dictionary_String_Int
if (type.IsGenericType)
{
name = type.Name.Substring(0, type.Name.IndexOf('`')) + "_" +
string.Join("_", type.GetGenericArguments().Select(SanitizeTypeName));
}
// 替换非法字符,保留 a-zA-Z0-9_$
name = Regex.Replace(name, @"[^a-zA-Z0-9_$]", "_");
// 开头不能是数字
if (!Regex.IsMatch(name, @"^[a-zA-Z$_]"))
{
name = "_" + name;
}
return name;
}
}
public class LiteDbDictionary<TKey, TValue> : IDictionary<TKey, TValue>,IReadOnlyDictionary<TKey,TValue>, IDisposable where TKey : notnull
2025-04-17 19:36:08 +08:00
{
// 内部的 LiteDB 数据库实例
private readonly LiteDatabase _db;
// LiteDB 集合名称可以自定义,默认为 "kv"
private readonly ILiteCollection<KeyValueItem> _collection;
// 内部使用的键值项POCO类
2025-06-09 15:55:24 +08:00
private record KeyValueItem
2025-04-17 19:36:08 +08:00
{
// LiteDB 将此字段作为文档的 _id主键
[BsonId] public TKey Key { get; set; }
public TValue Value { get; set; }
}
2025-06-09 15:55:24 +08:00
public LiteDbDictionary(ILogger<LiteDbDictionary<TKey,TValue>> logger=null)
2025-04-17 19:36:08 +08:00
{
2025-06-09 15:55:24 +08:00
/*
2025-04-17 19:36:08 +08:00
_db = new LiteDatabase(":memory:");
_collection = _db.GetCollection<KeyValueItem>(typeof(TValue).Name);
_collection.EnsureIndex(x => x.Key);
2025-06-09 15:55:24 +08:00
*/
// 获取程序集名作为默认数据库名
var asmName = Assembly.GetEntryAssembly()?.GetName().Name
?? Assembly.GetExecutingAssembly().GetName().Name
?? "LiteDb";
// 优先使用环境变量控制根目录
var rootDir = Environment.GetEnvironmentVariable("DATA_DIR");
// 默认持久化目录(跨平台)
if (string.IsNullOrWhiteSpace(rootDir))
{
#if UNITY_5_3_OR_NEWER
rootDir = Path.Combine(UnityEngine.Application.persistentDataPath, asmName);
#else
if (OperatingSystem.IsWindows())
rootDir =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), asmName);
else
rootDir =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), asmName);
#endif
}
Directory.CreateDirectory(rootDir); // 确保目录存在
var dbFilePath = Path.Combine(rootDir, $"{asmName}.db");
var connectionString = $"Filename={dbFilePath};Connection=shared";
logger?.LogInformation($"LiteDbDictionary<{typeof(TKey).Name},{typeof(TValue).Name}> 数据库文件: {dbFilePath}");
_db = new LiteDatabase(connectionString);
_collection = _db.GetCollection<KeyValueItem>($"{LiteDbHelper.GetReadableCollectionName<TKey>()}_{LiteDbHelper.GetReadableCollectionName<TValue>()}");
_collection.EnsureIndex(x => x.Key);
2025-04-17 19:36:08 +08:00
}
/// <summary>
/// 构造函数
/// connectionString 可以是磁盘文件(如 "Filename=MyData.db;Mode=Shared"
/// 或内存数据库(如 ":memory:"
/// </summary>
/// <param name="connectionString">数据库连接字符串</param>
/// <param name="collectionName">集合名称,默认 "kv"</param>
public LiteDbDictionary(string connectionString, string collectionName = "kv")
{
_db = new LiteDatabase(connectionString);
_collection = _db.GetCollection<KeyValueItem>(collectionName);
// 确保对键生成索引
_collection.EnsureIndex(x => x.Key);
}
#region IDictionary
public TValue this[TKey key]
{
get
{
var bsonId = _db.Mapper.Serialize(typeof(TKey), key);
var item = _collection.FindById(bsonId);
if (item == null) throw new KeyNotFoundException($"Key '{key}' not found.");
return item.Value;
}
set
{
var item = new KeyValueItem { Key = key, Value = value };
_collection.Upsert(item);
}
}
2025-06-09 15:55:24 +08:00
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
2025-04-17 19:36:08 +08:00
public ICollection<TKey> Keys => _collection.FindAll().Select(x => x.Key).ToList();
public ICollection<TValue> Values => _collection.FindAll().Select(x => x.Value).ToList();
public int Count => _collection.Count();
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
if (ContainsKey(key))
throw new ArgumentException($"An element with the key '{key}' already exists.");
_collection.Insert(new KeyValueItem { Key = key, Value = value });
}
public bool ContainsKey(TKey key)
{
2025-06-09 15:55:24 +08:00
var bsonKey = _db.Mapper.Serialize(typeof(TKey), key);
return _collection.FindById(bsonKey) != null; // ✅ 更安全
2025-04-17 19:36:08 +08:00
}
public bool Remove(TKey key)
{
var bsonKey = _db.Mapper.Serialize(typeof(TKey), key);
return _collection.Delete(bsonKey);
}
public bool TryGetValue(TKey key, out TValue value)
{
var bsonKey = _db.Mapper.Serialize(typeof(TKey), key);
var doc = _collection.FindById(bsonKey);
if (doc == null)
{
value = default;
return false;
}
value = doc.Value;
return true;
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_collection.DeleteAll();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
if (TryGetValue(item.Key, out var val))
return EqualityComparer<TValue>.Default.Equals(val, item.Value);
return false;
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
foreach (var kv in this)
{
array[arrayIndex++] = kv;
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (Contains(item))
return Remove(item.Key);
return false;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (var item in _collection.FindAll())
{
yield return new KeyValuePair<TKey, TValue>(item.Key, item.Value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#region IDisposable
public void Dispose()
{
_db?.Dispose();
}
#endregion
}
}