C#实现文件数据库

如果你需要一个简单的磁盘文件索引数据库,这篇文章可以帮助你。

文件数据库描述:

  • 每个文档对象保存为一个独立文件,例如一篇博客。
  • 文件内容序列化支持XML或JSON。
  • 支持基本的CRUD操作。
/// <summary>
  /// 文件数据库,这是一个抽象类。
  /// </summary>
  public abstract class FileDatabase
  {
    #region Fields

    /// <summary>
    /// 文件数据库操作锁
    /// </summary>
    protected static readonly object operationLock = new object();
    private static HashSet<char> invalidFileNameChars;

    static FileDatabase()
    {
      invalidFileNameChars = new HashSet<char>() { '\0', ' ', '.', '$', '/', '\\' };
      foreach (var c in Path.GetInvalidPathChars()) { invalidFileNameChars.Add(c); }
      foreach (var c in Path.GetInvalidFileNameChars()) { invalidFileNameChars.Add(c); }
    }

    /// <summary>
    /// 文件数据库
    /// </summary>
    /// <param name="directory">数据库文件所在目录</param>
    protected FileDatabase(string directory)
    {
      Directory = directory;
    }

    #endregion

    #region Properties

    /// <summary>
    /// 数据库文件所在目录
    /// </summary>
    public virtual string Directory { get; private set; }

    /// <summary>
    /// 是否输出缩进
    /// </summary>
    public virtual bool OutputIndent { get; set; }

    /// <summary>
    /// 文件扩展名
    /// </summary>
    public virtual string FileExtension { get; set; }

    #endregion

    #region Public Methods

    /// <summary>
    /// 保存文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="document">文档对象</param>
    /// <returns>文档ID</returns>
    public virtual string Save<TDocument>(TDocument document)
    {
      return Save<TDocument>(ObjectId.NewObjectId().ToString(), document);
    }

    /// <summary>
    /// 保存文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="id">文档ID</param>
    /// <param name="document">文档对象</param>
    /// <returns>文档ID</returns>
    public virtual string Save<TDocument>(string id, TDocument document)
    {
      if (string.IsNullOrEmpty(id))
        throw new ArgumentNullException("id");

      if (document == null)
        throw new ArgumentNullException("document");

      Delete<TDocument>(id);

      try
      {
        string fileName = GenerateFileFullPath<TDocument>(id);
        string output = Serialize(document);

        lock (operationLock)
        {
          System.IO.FileInfo info = new System.IO.FileInfo(fileName);
          System.IO.Directory.CreateDirectory(info.Directory.FullName);
          System.IO.File.WriteAllText(fileName, output);
        }
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          string.Format(CultureInfo.InvariantCulture, 
          "Save document failed with id [{0}].", id), ex);
      }

      return id;
    }

    /// <summary>
    /// 根据文档ID查找文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="id">文档ID</param>
    /// <returns>文档对象</returns>
    public virtual TDocument FindOneById<TDocument>(string id)
    {
      if (string.IsNullOrEmpty(id))
        throw new ArgumentNullException("id");

      try
      {
        string fileName = GenerateFileFullPath<TDocument>(id);
        if (File.Exists(fileName))
        {
          string fileData = File.ReadAllText(fileName);
          return Deserialize<TDocument>(fileData);
        }

        return default(TDocument);
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          string.Format(CultureInfo.InvariantCulture, 
          "Find document by id [{0}] failed.", id), ex);
      }
    }

    /// <summary>
    /// 查找指定类型的所有文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <returns>文档对象序列</returns>
    public virtual IEnumerable<TDocument> FindAll<TDocument>()
    {
      try
      {
        string[] files = System.IO.Directory.GetFiles(
          GenerateFilePath<TDocument>(), 
          "*." + FileExtension, 
          SearchOption.TopDirectoryOnly);

        List<TDocument> list = new List<TDocument>();
        foreach (string fileName in files)
        {
          string fileData = File.ReadAllText(fileName);
          TDocument document = Deserialize<TDocument>(fileData);
          if (document != null)
          {
            list.Add(document);
          }
        }

        return list;
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          "Find all documents failed.", ex);
      }
    }

    /// <summary>
    /// 根据指定文档ID删除文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="id">文档ID</param>
    public virtual void Delete<TDocument>(string id)
    {
      if (string.IsNullOrEmpty(id))
        throw new ArgumentNullException("id");

      try
      {
        string fileName = GenerateFileFullPath<TDocument>(id);
        if (File.Exists(fileName))
        {
          lock (operationLock)
          {
            File.Delete(fileName);
          }
        }
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          string.Format(CultureInfo.InvariantCulture, 
          "Delete document by id [{0}] failed.", id), ex);
      }
    }

    /// <summary>
    /// 删除所有指定类型的文档
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    public virtual void DeleteAll<TDocument>()
    {
      try
      {
        string[] files = System.IO.Directory.GetFiles(
          GenerateFilePath<TDocument>(), "*." + FileExtension, 
          SearchOption.TopDirectoryOnly);

        foreach (string fileName in files)
        {
          lock (operationLock)
          {
            File.Delete(fileName);
          }
        }
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          "Delete all documents failed.", ex);
      }
    }

    /// <summary>
    /// 获取指定类型文档的数量
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <returns>文档的数量</returns>
    public virtual int Count<TDocument>()
    {
      try
      {
        string[] files = System.IO.Directory.GetFiles(
          GenerateFilePath<TDocument>(), 
          "*." + FileExtension, SearchOption.TopDirectoryOnly);
        if (files != null)
        {
          return files.Length;
        }
        else
        {
          return 0;
        }
      }
      catch (Exception ex)
      {
        throw new FileDatabaseException(
          "Count all documents failed.", ex);
      }
    }

    #endregion

    #region Protected Methods

    /// <summary>
    /// 生成文件全路径
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="id">文档ID</param>
    /// <returns>文件路径</returns>
    protected virtual string GenerateFileFullPath<TDocument>(string id)
    {
      return Path.Combine(GenerateFilePath<TDocument>(), 
        GenerateFileName<TDocument>(id));
    }

    /// <summary>
    /// 生成文件路径
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <returns>文件路径</returns>
    protected virtual string GenerateFilePath<TDocument>()
    {
      return Path.Combine(this.Directory, typeof(TDocument).Name);
    }

    /// <summary>
    /// 生成文件名
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="id">文档ID</param>
    /// <returns>文件名</returns>
    protected virtual string GenerateFileName<TDocument>(string id)
    {
      if (string.IsNullOrEmpty(id))
        throw new ArgumentNullException("id");

      foreach (char c in id)
      {
        if (invalidFileNameChars.Contains(c))
        {
          throw new FileDatabaseException(
            string.Format(CultureInfo.InvariantCulture, 
            "The character '{0}' is not a valid file name identifier.", c));
        }
      }

      return string.Format(CultureInfo.InvariantCulture, "{0}.{1}", id, FileExtension);
    }

    /// <summary>
    /// 将指定的文档对象序列化至字符串
    /// </summary>
    /// <param name="value">指定的文档对象</param>
    /// <returns>文档对象序列化后的字符串</returns>
    protected abstract string Serialize(object value);

    /// <summary>
    /// 将字符串反序列化成文档对象
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="data">字符串</param>
    /// <returns>文档对象</returns>
    protected abstract TDocument Deserialize<TDocument>(string data);

    #endregion
  }

XML文件数据库实现

/// <summary>
  /// XML文件数据库
  /// </summary>
  public class XmlDatabase : FileDatabase
  {
    /// <summary>
    /// XML文件数据库
    /// </summary>
    /// <param name="directory">数据库文件所在目录</param>
    public XmlDatabase(string directory)
      : base(directory)
    {
      FileExtension = @"xml";
    }

    /// <summary>
    /// 将指定的文档对象序列化至字符串
    /// </summary>
    /// <param name="value">指定的文档对象</param>
    /// <returns>
    /// 文档对象序列化后的字符串
    /// </returns>
    protected override string Serialize(object value)
    {
      if (value == null)
        throw new ArgumentNullException("value");

      using (StringWriterWithEncoding sw = new StringWriterWithEncoding(Encoding.UTF8))
      {
        XmlSerializer serializer = new XmlSerializer(value.GetType());
        serializer.Serialize(sw, value);
        return sw.ToString();
      }
    }

    /// <summary>
    /// 将字符串反序列化成文档对象
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="data">字符串</param>

/// <returns> /// 文档对象 /// </returns> protected override TDocument Deserialize<TDocument>(string data) { if (string.IsNullOrEmpty(data)) throw new ArgumentNullException("data"); using (StringReader sr = new StringReader(data)) { XmlSerializer serializer = new XmlSerializer(typeof(TDocument)); return (TDocument)serializer.Deserialize(sr); } } }

JSON文件数据库实现

/// <summary>
  /// JSON文件数据库
  /// </summary>
  public class JsonDatabase : FileDatabase
  {
    /// <summary>
    /// JSON文件数据库
    /// </summary>
    /// <param name="directory">数据库文件所在目录</param>
    public JsonDatabase(string directory)
      : base(directory)
    {
      FileExtension = @"json";
    }

    /// <summary>
    /// 将指定的文档对象序列化至字符串
    /// </summary>
    /// <param name="value">指定的文档对象</param>
    /// <returns>
    /// 文档对象序列化后的字符串
    /// </returns>
    protected override string Serialize(object value)
    {
      if (value == null)
        throw new ArgumentNullException("value");

      return JsonConvert.SerializeObject(value, OutputIndent);
    }

    /// <summary>
    /// 将字符串反序列化成文档对象
    /// </summary>
    /// <typeparam name="TDocument">文档类型</typeparam>
    /// <param name="data">字符串</param>
    /// <returns>
    /// 文档对象
    /// </returns>
    protected override TDocument Deserialize<TDocument>(string data)
    {
      if (string.IsNullOrEmpty(data))
        throw new ArgumentNullException("data");

      return JsonConvert.DeserializeObject<TDocument>(data);
    }
  }

 Test Double

[Serializable]
  public class Cat
  {
    public Cat()
    {
      Id = ObjectId.NewObjectId().ToString();
    }

    public Cat(string id)
    {
      Id = id;
    }

    public string Name { get; set; }
    public int Legs { get; set; }

    public string Id { get; set; }

    public override string ToString()
    {
      return string.Format("DocumentId={0}, Name={1}, Legs={2}", Id, Name, Legs);
    }
  }

使用举例

class Program
  {
    static void Main(string[] args)
    {
      TestJsonDatabase();
      TestXmlDatabase();

      Console.ReadKey();
    }

    private static void TestJsonDatabase()
    {
      JsonDatabase db = new JsonDatabase(@"C:\tmp");
      db.OutputIndent = true;

      Cat origin = new Cat() { Name = "Garfield", Legs = 4 };
      db.Save<Cat>(origin);

      db.Save<Cat>(origin.Id, origin);
      db.Delete<Cat>(origin.Id);
    }

    private static void TestXmlDatabase()
    {
      XmlDatabase db = new XmlDatabase(@"C:\tmp");
      db.OutputIndent = true;

      Cat origin = new Cat() { Name = "Garfield", Legs = 4 };
      db.Save<Cat>(origin);

      db.Save<Cat>(origin.Id, origin);
      db.Delete<Cat>(origin.Id);
    }
  }

 

posted @ 2013-08-29 15:14  Net-Spider  阅读(956)  评论(0)    收藏  举报