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>() { '', ' ', '.', '$', '/', '\' };
 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:	mp");
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:	mp");
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 类型,其包含:

  1. 4 Bytes 自纪元时间开始的秒数
  2. 3 Bytes 机器描述符
  3. 2 Bytes 进程ID
  4. 3 Bytes 随机数

ObjectId的存储使用Byte数组,而其展现需将Byte数组转换成字符串进行显示,所以通常我们看到的ObjectId都类似于:

ObjectId("507f191e810c19729de860ea")

原样例代码存在问题,已删除,请直接参考官方代码。

https://github.com/mongodb/mongo-csharp-driver

https://github.com/mongodb/mongo-csharp-driver/blob/ec74978f7e827515f29cc96fba0c727828e8df7c/src/MongoDB.Bson/ObjectModel/ObjectId.cs

 

出处: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/

======================================================================

希望以后可以实现、优化的功能:

  1. 文件使用固定的扩展名
  2. 数据文件内部,可以分文件头和文件体。
  3. 文件头:记录文件类型、列定义、列说明、作者、创建时间、最后更新时间等,都使用固定长度的字节存储。
  4. 文件体:根据文件头中定义的列,保存数据
  5. 支持索引
  6. 支持自定义不同的列
  7. 支持SQL查询功能

以上功能慢慢增加和优化。

原文地址:https://www.cnblogs.com/mq0036/p/13261362.html