C#实现文件数据库
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。
如果你需要一个简单的磁盘文件索引数据库,这篇文章可以帮助你。
文件数据库描述:
- 每个文档对象保存为一个独立文件,例如一篇博客。
- 文件内容序列化支持XML或JSON。
- 支持基本的CRUD操作。
文件数据库抽象类实现
1 /// <summary> 2 /// 文件数据库,这是一个抽象类。 3 /// </summary> 4 public abstract class FileDatabase 5 { 6 #region Fields 7 8 /// <summary> 9 /// 文件数据库操作锁 10 /// </summary> 11 protected static readonly object operationLock = new object(); 12 private static HashSet<char> invalidFileNameChars; 13 14 static FileDatabase() 15 { 16 invalidFileNameChars = new HashSet<char>() { '\0', ' ', '.', '$', '/', '\\' }; 17 foreach (var c in Path.GetInvalidPathChars()) { invalidFileNameChars.Add(c); } 18 foreach (var c in Path.GetInvalidFileNameChars()) { invalidFileNameChars.Add(c); } 19 } 20 21 /// <summary> 22 /// 文件数据库 23 /// </summary> 24 /// <param name="directory">数据库文件所在目录</param> 25 protected FileDatabase(string directory) 26 { 27 Directory = directory; 28 } 29 30 #endregion 31 32 #region Properties 33 34 /// <summary> 35 /// 数据库文件所在目录 36 /// </summary> 37 public virtual string Directory { get; private set; } 38 39 /// <summary> 40 /// 是否输出缩进 41 /// </summary> 42 public virtual bool OutputIndent { get; set; } 43 44 /// <summary> 45 /// 文件扩展名 46 /// </summary> 47 public virtual string FileExtension { get; set; } 48 49 #endregion 50 51 #region Public Methods 52 53 /// <summary> 54 /// 保存文档 55 /// </summary> 56 /// <typeparam name="TDocument">文档类型</typeparam> 57 /// <param name="document">文档对象</param> 58 /// <returns>文档ID</returns> 59 public virtual string Save<TDocument>(TDocument document) 60 { 61 return Save<TDocument>(ObjectId.NewObjectId().ToString(), document); 62 } 63 64 /// <summary> 65 /// 保存文档 66 /// </summary> 67 /// <typeparam name="TDocument">文档类型</typeparam> 68 /// <param name="id">文档ID</param> 69 /// <param name="document">文档对象</param> 70 /// <returns>文档ID</returns> 71 public virtual string Save<TDocument>(string id, TDocument document) 72 { 73 if (string.IsNullOrEmpty(id)) 74 throw new ArgumentNullException("id"); 75 76 if (document == null) 77 throw new ArgumentNullException("document"); 78 79 Delete<TDocument>(id); 80 81 try 82 { 83 string fileName = GenerateFileFullPath<TDocument>(id); 84 string output = Serialize(document); 85 86 lock (operationLock) 87 { 88 System.IO.FileInfo info = new System.IO.FileInfo(fileName); 89 System.IO.Directory.CreateDirectory(info.Directory.FullName); 90 System.IO.File.WriteAllText(fileName, output); 91 } 92 } 93 catch (Exception ex) 94 { 95 throw new FileDatabaseException( 96 string.Format(CultureInfo.InvariantCulture, 97 "Save document failed with id [{0}].", id), ex); 98 } 99 100 return id; 101 } 102 103 /// <summary> 104 /// 根据文档ID查找文档 105 /// </summary> 106 /// <typeparam name="TDocument">文档类型</typeparam> 107 /// <param name="id">文档ID</param> 108 /// <returns>文档对象</returns> 109 public virtual TDocument FindOneById<TDocument>(string id) 110 { 111 if (string.IsNullOrEmpty(id)) 112 throw new ArgumentNullException("id"); 113 114 try 115 { 116 string fileName = GenerateFileFullPath<TDocument>(id); 117 if (File.Exists(fileName)) 118 { 119 string fileData = File.ReadAllText(fileName); 120 return Deserialize<TDocument>(fileData); 121 } 122 123 return default(TDocument); 124 } 125 catch (Exception ex) 126 { 127 throw new FileDatabaseException( 128 string.Format(CultureInfo.InvariantCulture, 129 "Find document by id [{0}] failed.", id), ex); 130 } 131 } 132 133 /// <summary> 134 /// 查找指定类型的所有文档 135 /// </summary> 136 /// <typeparam name="TDocument">文档类型</typeparam> 137 /// <returns>文档对象序列</returns> 138 public virtual IEnumerable<TDocument> FindAll<TDocument>() 139 { 140 try 141 { 142 string[] files = System.IO.Directory.GetFiles( 143 GenerateFilePath<TDocument>(), 144 "*." + FileExtension, 145 SearchOption.TopDirectoryOnly); 146 147 List<TDocument> list = new List<TDocument>(); 148 foreach (string fileName in files) 149 { 150 string fileData = File.ReadAllText(fileName); 151 TDocument document = Deserialize<TDocument>(fileData); 152 if (document != null) 153 { 154 list.Add(document); 155 } 156 } 157 158 return list; 159 } 160 catch (Exception ex) 161 { 162 throw new FileDatabaseException( 163 "Find all documents failed.", ex); 164 } 165 } 166 167 /// <summary> 168 /// 根据指定文档ID删除文档 169 /// </summary> 170 /// <typeparam name="TDocument">文档类型</typeparam> 171 /// <param name="id">文档ID</param> 172 public virtual void Delete<TDocument>(string id) 173 { 174 if (string.IsNullOrEmpty(id)) 175 throw new ArgumentNullException("id"); 176 177 try 178 { 179 string fileName = GenerateFileFullPath<TDocument>(id); 180 if (File.Exists(fileName)) 181 { 182 lock (operationLock) 183 { 184 File.Delete(fileName); 185 } 186 } 187 } 188 catch (Exception ex) 189 { 190 throw new FileDatabaseException( 191 string.Format(CultureInfo.InvariantCulture, 192 "Delete document by id [{0}] failed.", id), ex); 193 } 194 } 195 196 /// <summary> 197 /// 删除所有指定类型的文档 198 /// </summary> 199 /// <typeparam name="TDocument">文档类型</typeparam> 200 public virtual void DeleteAll<TDocument>() 201 { 202 try 203 { 204 string[] files = System.IO.Directory.GetFiles( 205 GenerateFilePath<TDocument>(), "*." + FileExtension, 206 SearchOption.TopDirectoryOnly); 207 208 foreach (string fileName in files) 209 { 210 lock (operationLock) 211 { 212 File.Delete(fileName); 213 } 214 } 215 } 216 catch (Exception ex) 217 { 218 throw new FileDatabaseException( 219 "Delete all documents failed.", ex); 220 } 221 } 222 223 /// <summary> 224 /// 获取指定类型文档的数量 225 /// </summary> 226 /// <typeparam name="TDocument">文档类型</typeparam> 227 /// <returns>文档的数量</returns> 228 public virtual int Count<TDocument>() 229 { 230 try 231 { 232 string[] files = System.IO.Directory.GetFiles( 233 GenerateFilePath<TDocument>(), 234 "*." + FileExtension, SearchOption.TopDirectoryOnly); 235 if (files != null) 236 { 237 return files.Length; 238 } 239 else 240 { 241 return 0; 242 } 243 } 244 catch (Exception ex) 245 { 246 throw new FileDatabaseException( 247 "Count all documents failed.", ex); 248 } 249 } 250 251 #endregion 252 253 #region Protected Methods 254 255 /// <summary> 256 /// 生成文件全路径 257 /// </summary> 258 /// <typeparam name="TDocument">文档类型</typeparam> 259 /// <param name="id">文档ID</param> 260 /// <returns>文件路径</returns> 261 protected virtual string GenerateFileFullPath<TDocument>(string id) 262 { 263 return Path.Combine(GenerateFilePath<TDocument>(), 264 GenerateFileName<TDocument>(id)); 265 } 266 267 /// <summary> 268 /// 生成文件路径 269 /// </summary> 270 /// <typeparam name="TDocument">文档类型</typeparam> 271 /// <returns>文件路径</returns> 272 protected virtual string GenerateFilePath<TDocument>() 273 { 274 return Path.Combine(this.Directory, typeof(TDocument).Name); 275 } 276 277 /// <summary> 278 /// 生成文件名 279 /// </summary> 280 /// <typeparam name="TDocument">文档类型</typeparam> 281 /// <param name="id">文档ID</param> 282 /// <returns>文件名</returns> 283 protected virtual string GenerateFileName<TDocument>(string id) 284 { 285 if (string.IsNullOrEmpty(id)) 286 throw new ArgumentNullException("id"); 287 288 foreach (char c in id) 289 { 290 if (invalidFileNameChars.Contains(c)) 291 { 292 throw new FileDatabaseException( 293 string.Format(CultureInfo.InvariantCulture, 294 "The character '{0}' is not a valid file name identifier.", c)); 295 } 296 } 297 298 return string.Format(CultureInfo.InvariantCulture, "{0}.{1}", id, FileExtension); 299 } 300 301 /// <summary> 302 /// 将指定的文档对象序列化至字符串 303 /// </summary> 304 /// <param name="value">指定的文档对象</param> 305 /// <returns>文档对象序列化后的字符串</returns> 306 protected abstract string Serialize(object value); 307 308 /// <summary> 309 /// 将字符串反序列化成文档对象 310 /// </summary> 311 /// <typeparam name="TDocument">文档类型</typeparam> 312 /// <param name="data">字符串</param> 313 /// <returns>文档对象</returns> 314 protected abstract TDocument Deserialize<TDocument>(string data); 315 316 #endregion 317 }
XML文件数据库实现
1 /// <summary> 2 /// XML文件数据库 3 /// </summary> 4 public class XmlDatabase : FileDatabase 5 { 6 /// <summary> 7 /// XML文件数据库 8 /// </summary> 9 /// <param name="directory">数据库文件所在目录</param> 10 public XmlDatabase(string directory) 11 : base(directory) 12 { 13 FileExtension = @"xml"; 14 } 15 16 /// <summary> 17 /// 将指定的文档对象序列化至字符串 18 /// </summary> 19 /// <param name="value">指定的文档对象</param> 20 /// <returns> 21 /// 文档对象序列化后的字符串 22 /// </returns> 23 protected override string Serialize(object value) 24 { 25 if (value == null) 26 throw new ArgumentNullException("value"); 27 28 using (StringWriterWithEncoding sw = new StringWriterWithEncoding(Encoding.UTF8)) 29 { 30 XmlSerializer serializer = new XmlSerializer(value.GetType()); 31 serializer.Serialize(sw, value); 32 return sw.ToString(); 33 } 34 } 35 36 /// <summary> 37 /// 将字符串反序列化成文档对象 38 /// </summary> 39 /// <typeparam name="TDocument">文档类型</typeparam> 40 /// <param name="data">字符串</param> 41 /// <returns> 42 /// 文档对象 43 /// </returns> 44 protected override TDocument Deserialize<TDocument>(string data) 45 { 46 if (string.IsNullOrEmpty(data)) 47 throw new ArgumentNullException("data"); 48 49 using (StringReader sr = new StringReader(data)) 50 { 51 XmlSerializer serializer = new XmlSerializer(typeof(TDocument)); 52 return (TDocument)serializer.Deserialize(sr); 53 } 54 } 55 }
JSON文件数据库实现
1 /// <summary> 2 /// JSON文件数据库 3 /// </summary> 4 public class JsonDatabase : FileDatabase 5 { 6 /// <summary> 7 /// JSON文件数据库 8 /// </summary> 9 /// <param name="directory">数据库文件所在目录</param> 10 public JsonDatabase(string directory) 11 : base(directory) 12 { 13 FileExtension = @"json"; 14 } 15 16 /// <summary> 17 /// 将指定的文档对象序列化至字符串 18 /// </summary> 19 /// <param name="value">指定的文档对象</param> 20 /// <returns> 21 /// 文档对象序列化后的字符串 22 /// </returns> 23 protected override string Serialize(object value) 24 { 25 if (value == null) 26 throw new ArgumentNullException("value"); 27 28 return JsonConvert.SerializeObject(value, OutputIndent); 29 } 30 31 /// <summary> 32 /// 将字符串反序列化成文档对象 33 /// </summary> 34 /// <typeparam name="TDocument">文档类型</typeparam> 35 /// <param name="data">字符串</param> 36 /// <returns> 37 /// 文档对象 38 /// </returns> 39 protected override TDocument Deserialize<TDocument>(string data) 40 { 41 if (string.IsNullOrEmpty(data)) 42 throw new ArgumentNullException("data"); 43 44 return JsonConvert.DeserializeObject<TDocument>(data); 45 } 46 }
Test Double
1 [Serializable] 2 public class Cat 3 { 4 public Cat() 5 { 6 Id = ObjectId.NewObjectId().ToString(); 7 } 8 9 public Cat(string id) 10 { 11 Id = id; 12 } 13 14 public string Name { get; set; } 15 public int Legs { get; set; } 16 17 public string Id { get; set; } 18 19 public override string ToString() 20 { 21 return string.Format("DocumentId={0}, Name={1}, Legs={2}", Id, Name, Legs); 22 } 23 }
使用举例
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TestJsonDatabase(); 6 TestXmlDatabase(); 7 8 Console.ReadKey(); 9 } 10 11 private static void TestJsonDatabase() 12 { 13 JsonDatabase db = new JsonDatabase(@"C:\tmp"); 14 db.OutputIndent = true; 15 16 Cat origin = new Cat() { Name = "Garfield", Legs = 4 }; 17 db.Save<Cat>(origin); 18 19 db.Save<Cat>(origin.Id, origin); 20 db.Delete<Cat>(origin.Id); 21 } 22 23 private static void TestXmlDatabase() 24 { 25 XmlDatabase db = new XmlDatabase(@"C:\tmp"); 26 db.OutputIndent = true; 27 28 Cat origin = new Cat() { Name = "Garfield", Legs = 4 }; 29 db.Save<Cat>(origin); 30 31 db.Save<Cat>(origin.Id, origin); 32 db.Delete<Cat>(origin.Id); 33 } 34 }
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。
出处:https://www.cnblogs.com/gaochundong/archive/2013/04/24/csharp_file_database.html
=========================================================================================
下面是对上文中的一些补充说明:
ObjectId参考:C# 生成 MongoDB 中的 ObjectId
ObjectId介绍
在MongoDB中,文档(document)在集合(collection)中的存储需要一个唯一的_id字段作为主键。这个_id默认使用ObjectId来定义,因为ObjectId定义的足够短小,并尽最大可能的保持唯一性,同时能被快速的生成。
ObjectId 是一个 12 Bytes 的 BSON 类型,其包含:
- 4 Bytes 自纪元时间开始的秒数
- 3 Bytes 机器描述符
- 2 Bytes 进程ID
- 3 Bytes 随机数
ObjectId的存储使用Byte数组,而其展现需将Byte数组转换成字符串进行显示,所以通常我们看到的ObjectId都类似于:
ObjectId("507f191e810c19729de860ea")
原样例代码存在问题,已删除,请直接参考官方代码。
https://github.com/mongodb/mongo-csharp-driver
出处:https://www.cnblogs.com/gaochundong/archive/2013/04/24/csharp_generate_mongodb_objectid.html
可考虑使用guid生成ObjectId
===================================================
FileDatabaseException
public class FileDatabaseException : Exception { public FileDatabaseException(string message) : base(message) { } public FileDatabaseException(string message, Exception innerException) : base(message, innerException) { } }
===================================================
StringWriterWithEncoding
public sealed class StringWriterWithEncoding : StringWriter { private readonly Encoding encoding; public StringWriterWithEncoding (Encoding encoding) { this.encoding = encoding; } public override Encoding Encoding { get { return encoding; } } }
参考链接:http://www.yoda.arachsys.com/csharp/miscutil/
======================================================================
希望以后可以实现、优化的功能:
- 文件使用固定的扩展名
- 数据文件内部,可以分文件头和文件体。
- 文件头:记录文件类型、列定义、列说明、作者、创建时间、最后更新时间等,都使用固定长度的字节存储。
- 文件体:根据文件头中定义的列,保存数据
- 支持索引
- 支持自定义不同的列
- 支持SQL查询功能
以上功能慢慢增加和优化。
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/13261362.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!
posted on 2020-07-07 16:01 jack_Meng 阅读(1197) 评论(0) 编辑 收藏 举报