1. 程式人生 > >基於本地文件系統的LocalDB

基於本地文件系統的LocalDB

root 構造函數 -s oot region pan pri 指定 void

零、前言

  之前寫一些小工具的時候,需要用到數據存儲方面的技術,但是用數據庫又覺得太大了,本地文件存儲txt文件存儲又不是很規範,於是乎想到了去編寫一個簡單的基於本地文件系統的數據存儲庫,暫且叫它localdb吧,總之也是為了方便使用,特別是數據量不大,但是又特別想要本地數據存儲的。(拋開access不說)

一、工具

  工欲善其事必先利其器,存儲數據必然要有一個標準的數據格式,首先讓我想到的就是json,xml,原因不必多說。所以下面編寫的數據主要以json作為存儲格式。json操作用Newtonjson,操作方便簡單。

二、文件系統的操作(LocalFileDB)

  本地數據庫,必不可少對本地文件進行操作,所以對本地文件的讀取創建統一管理,話不多說,直接上代碼。

  首先添加自定義的異常:

技術分享
    /// <summary>
    /// 文件數據庫異常
    /// </summary>
    public class FileDatabaseException : Exception
    {
        public FileDatabaseException(string message)
            : base(message)
        { }

        public FileDatabaseException(string message, Exception innerException)
            : 
base(message, innerException) { } }
View Code

  然後是具體操作文件的代碼(這塊有參考別人的,僅供學習使用,如有侵權,請聯系我):

技術分享
    /// <summary>
    /// 文件數據庫,這是一個抽象類。(之前做的拿來用的,也可以應用於xml,但已有linq to xml,所以這邊暫時僅用於json)
    /// </summary>
    public abstract class FileDatabase<TEntity>
    {
        #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) { MyDirectory = directory; } public FileDatabase() { } #endregion #region Properties /// <summary> /// 數據庫文件所在目錄 /// </summary> public virtual string MyDirectory { get; protected set; } /// <summary> /// 文件擴展名 /// </summary> public virtual string FileExtension { get; set; } public virtual bool IsIndent { get; set; } #endregion #region Public Methods /// <summary> /// 保存文檔 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <param name="id">文檔ID</param> /// <param name="document">文檔對象</param> /// <returns>文檔ID</returns> public virtual string Save(string id, TEntity document) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); if (document == null) throw new ArgumentNullException("document"); Delete(id); try { string fileName = GenerateFileFullPath(id); string output = Serialize(document, IsIndent); 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="TEntity">文檔類型</typeparam> /// <param name="id">文檔ID</param> /// <returns>文檔對象</returns> public virtual TEntity FindOneById(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); try { string fileName = GenerateFileFullPath(id); if (File.Exists(fileName)) { string fileData = File.ReadAllText(fileName); return Deserialize(fileData); } return default(TEntity); } catch (Exception ex) { throw new FileDatabaseException( string.Format(CultureInfo.InvariantCulture, "Find document by id [{0}] failed.", id), ex); } } /// <summary> /// 查找指定類型的所有文檔 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <returns>文檔對象序列</returns> public virtual IEnumerable<TEntity> FindAll() { try { List<TEntity> list = new List<TEntity>(); if (Directory.Exists(GenerateFilePath())) { string[] files = System.IO.Directory.GetFiles( GenerateFilePath(), "*." + FileExtension, SearchOption.TopDirectoryOnly); foreach (string fileName in files) { string fileData = File.ReadAllText(fileName); TEntity document = Deserialize(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="TEntity">文檔類型</typeparam> /// <param name="id">文檔ID</param> public virtual void Delete(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); try { string fileName = GenerateFileFullPath(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="TEntity">文檔類型</typeparam> public virtual void DeleteAll() { try { if (Directory.Exists(GenerateFilePath())) { string[] files = System.IO.Directory.GetFiles( GenerateFilePath(), "*." + 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="TEntity">文檔類型</typeparam> /// <returns>文檔的數量</returns> public virtual int Count() { try { if (Directory.Exists(GenerateFilePath())) { string[] files = System.IO.Directory.GetFiles( GenerateFilePath(), "*." + FileExtension, SearchOption.TopDirectoryOnly); if (files != null) { return files.Length; } else { return 0; } } return 0; } catch (Exception ex) { throw new FileDatabaseException( "Count all documents failed.", ex); } } #endregion #region Protected Methods /// <summary> /// 生成文件全路徑 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <param name="id">文檔ID</param> /// <returns>文件路徑</returns> protected virtual string GenerateFileFullPath(string id) { return Path.Combine(GenerateFilePath(), GenerateFileName(id)); } /// <summary> /// 生成文件路徑 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <returns>文件路徑</returns> protected virtual string GenerateFilePath() { return Path.Combine(this.MyDirectory, typeof(TEntity).Name); } /// <summary> /// 生成文件名 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <param name="id">文檔ID</param> /// <returns>文件名</returns> protected virtual string GenerateFileName(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, bool isIndent = false); /// <summary> /// 將字符串反序列化成文檔對象 /// </summary> /// <typeparam name="TEntity">文檔類型</typeparam> /// <param name="data">字符串</param> /// <returns>文檔對象</returns> protected abstract TEntity Deserialize(string data); #endregion }
View Code

三、存儲架構的設計

1、基礎設施的構建

  數據庫采用的是類似倉儲的設計,這樣對數據的管理比較直觀,並且易用。什麽是倉儲,園內有很多博客有闡述,在此就不贅述了。

  首先,要先有實體基類:

技術分享
    /// <summary>
    ///     可持久到數據庫的領域模型的基類。
    /// </summary>
    [Serializable]
    public abstract class EntityBase
    {
        #region 構造函數

        /// <summary>
        ///     數據實體基類
        /// </summary>
        protected EntityBase()
        {
        }

        #endregion
    }
View Code

  其次,要有倉儲接口:

技術分享
    /// <summary>
    /// 數據基礎操作規範
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public interface IRepository<TEntity> 
        where TEntity : EntityBase
    {
        /// <summary>
        /// 添加實體並提交到數據服務器
        /// </summary>
        /// <param name="item">需要添加數據項</param>
        /// <returns>受影響條數</returns>
        int Insert(TEntity item);

        /// <summary>
        /// 移除實體並提交到數據服務器
        /// 如果表存在約束,需要先刪除子表信息
        /// </summary>
        /// <param name="item">需要刪除的數據項</param>
        /// <returns>受影響條數</returns>
        int Delete(TEntity item);

        /// <summary>
        /// 修改實體並提交到數據服務器
        /// </summary>
        /// <param name="item">需要修改的數據項</param>
        /// <returns>受影響條數</returns>
        int Update(TEntity item);

        /// <summary>
        /// 得到指定的實體集合(延時結果集)
        /// </summary>
        /// <returns>實體集合</returns>
        IQueryable<TEntity> GetModel();

        /// <summary>
        /// 根據主鍵得到實體
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        TEntity Find(params object[] id);
    }
View Code

2、基於json倉儲的實現

  json實體基類:

技術分享
    /// <summary>
    /// Json實體基類
    /// </summary>
    public abstract class JsonEntity : EntityBase
    {
        private string id = PublicHelper.GuidGenerator();
        /// <summary>
        /// Json實體主鍵
        /// </summary>
        public string RootID
        {
            get { return id; }
            set { id = value; }
        }
    }
View Code

  json倉儲接口:

技術分享
    /// <summary>
    /// Json倉儲接口
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public interface IJsonRepository<TEntity>
        : IRepository<TEntity>
        where TEntity : JsonEntity
    {
        /// <summary>
        /// 是否縮進
        /// </summary>
        bool IsIndent { get; set; }
        /// <summary>
        /// 按文件存儲,直接刪除所有文件
        /// </summary>
        void DeleteAll();
        int Count();
    }
View Code

  json倉儲的具體實現:

技術分享
    /// <summary>
    /// json倉儲
    /// </summary>
    public class JsonRepository<TEntity> :
        FileDatabase<TEntity>, IJsonRepository<TEntity>
        where TEntity : JsonEntity,new()
    {
        string m_Directory = AppDomain.CurrentDomain.BaseDirectory;

        public new bool IsIndent
        {
            get
            {
                return base.IsIndent;
            }

            set
            {
                base.IsIndent = value;
            }
        }

        public JsonRepository(string directory)
            :base(directory)
        {
            FileExtension = @"db";
            this.m_Directory = directory;
        }

        public JsonRepository()
        {
            FileExtension = @"db";
            MyDirectory = m_Directory;
        }

        public int Delete(TEntity item)
        {
            Delete(item.RootID);

            return 1;
        }

        public TEntity Find(params object[] id)
        {
            return FindOneById(id[0].ToString());
        }

        public IQueryable<TEntity> GetModel()
        {
            return FindAll().AsQueryable();
        }

        public int Insert(TEntity item)
        {
            Save(item.RootID, item);

            return 1;
        }

        public int Update(TEntity item)
        {
            Save(item.RootID, item);

            return 1;
        }

        protected override string Serialize(object value, bool isIndent = false)
        {
            return JsonHelper.Serialize(value, isIndent);
        }

        protected override TEntity Deserialize(string data)
        {
            return JsonHelper.Deserialize<TEntity>(data);
        }
    }
View Code

四、工具類

  PublicHelper:

技術分享
    /// <summary>
    ///     公共輔助操作類
    /// </summary>
    public static class PublicHelper
    {
        #region 公共方法

        /// <summary>
        /// Guid生成器
        /// </summary>
        /// <returns></returns>
        public static string GuidGenerator()
        {
            return Guid.NewGuid().ToString("N");
        }
        #endregion
    }
View Code

  JsonHelper:

技術分享
    public class JsonHelper
    {
        /// <summary>
        /// 序列化json
        /// </summary>
        /// <param name="value">對象</param>
        /// <param name="isIndented">是否縮進</param>
        /// <returns></returns>
        public static string Serialize(object value, bool isIndented)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value is null.");
            }

            return JsonConvert.SerializeObject(value, isIndented ? Formatting.Indented : Formatting.None);
        }

        /// <summary>
        /// 將字符反序列化成對象
        /// </summary>
        /// <typeparam name="TEntity">泛型對象</typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public static TEntity Deserialize<TEntity>(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                throw new ArgumentNullException("data is null or empty.");
            }

            return JsonConvert.DeserializeObject<TEntity>(data);
        }
    }
View Code

五、運行測試

  測試代碼:

技術分享
    class Program
    {
        static void Main(string[] args)
        {
            IJsonRepository<Student> jsonRepository = new JsonRepository<Student>();

            jsonRepository.Insert(new Student()
            {
                SNo = 1,
                Name = "張三",
                Gender = "",
                Age = 18,
            });

            jsonRepository.Insert(new Student()
            {
                SNo = 2,
                Name = "李四",
                Gender = "",
                Age = 18,
            });

            jsonRepository.Insert(new Student()
            {
                SNo = 3,
                Name = "王二",
                Gender = "",
                Age = 18,
            });

            var entities = jsonRepository.GetModel();

            foreach (var item in entities)
            {
                Console.WriteLine(item.ToString());
            }

            Console.ReadKey();
        }
    }

    public class Student:JsonEntity
    {
        /// <summary>
        /// 學號
        /// </summary>
        public int SNo { get; set; }
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 性別
        /// </summary>
        public string Gender { get; set; }
        /// <summary>
        /// 年齡
        /// </summary>
        public int Age { get; set; }

        public override string ToString()
        {
            return string.Format("學號:{0};姓名:{1};性別:{2};年齡:{3}", this.SNo, this.Name, this.Gender, this.Age);
        }
    }
View Code

  技術分享

技術分享

技術分享

技術分享

六、結語

  整個本地文件系統數據就完成了,系統可以擴展為其它格式來進行數據存儲,只需要擴展倉儲就可以了,操作也是簡便,性能上的話,本地數據庫自然是比不上那些專用數據庫的,但是滿足日常肯定是沒有任何問題的。代碼比較完整,我就不上傳代碼了。

註:如有轉載請標註出處。

基於本地文件系統的LocalDB