推薦一個簡單好用的接口——字典序列化

 

一. 咱們的需求

     你是否和我同樣有以下的困擾:程序員

  •      你須要將一個類轉換爲XML或JSON存儲或傳輸,但總有你不想存儲或想特殊處理的字段,用序列化器自身的反射功能就看起來頗爲雞肋了。
  •      與MongoDB等鍵值對數據庫作交互,不少ORM框架都無效了,如何寫一個通用的數據接口層?並且不用去添加醜陋的"MongoIgnore"這樣的attribute?
  •      你要將一個對象的屬性「拷貝」到另一個對象,怎麼作?C語言的拷貝構造函數?那太原始了。
  •      界面綁定:總有一些綁定表達式,想經過動態的形式提交給框架,而不是寫死在xaml裏,那是否又得在C#裏寫一堆對象映射的代碼了?

     你們確定都遇到過和我類似的困擾,爲了存儲或讀取某個對象(好比從文件或數據庫裏讀取),你不得不寫下大量數據類型轉換和賦值語句,並且在程序中不能複用,這樣的代碼處處都是,真是代碼的臭味。 數據庫

     因爲鍵值對的概念已經深刻人心,因而便有了這樣一個叫作「字典序列化」的接口,該接口不是官方定義的,而是我根據實際需求總結的,同時我發現,它真的很是好用。數組

     廢話不說,先看該接口的定義:框架

  /// <summary>
    /// 進行字典序列化的接口,方便實現鍵值對的映射關係和重建
    /// </summary>
    public interface IDictionarySerializable
    {
        /// <summary>
        /// 從數據到字典
        /// </summary>
        /// <returns></returns>
        IDictionary<string, object> DictSerialize(Scenario scenario = Scenario.Database);

        /// <summary>
        /// 從字典到數據
        /// </summary>
        /// <param name="dicts">字典</param>
        /// <param name="scenario">應用場景</param>
        void DictDeserialize(IDictionary<string, object> dicts, Scenario scenario = Scenario.Database);
    }  

     它只有兩個方法,一個是從字典中讀取數據到實體類,另外一個是將實體類的數據寫入字典。你要作的,就是讓你的實體類實現這個接口。ide

     值得注意的是,函數參數中有一個Scenario枚舉,該枚舉指定了轉換的場景,例如數據庫和用戶界面,在轉換時可能就有必定的區別,程序員可經過實際狀況來肯定,具體緣由能夠看下邊的部分。你能夠傳入更多的參數,指示該接口的獨特序列化方法。函數

二. 如何使用?

      先定義一個實體類:Artical, 它是一個簡單的POCO類型, 包含Title等六個屬性,爲了節約篇幅,就不在此聲明它了。咱們先看以下的代碼:性能

  //define a entity named Artical with following propertynames
   public void DictDeserialize(IDictionary<string, object> dicts)
        {
            Title = (string)dicts["Title"];
            PublishTime = (DateTime)dicts["PublishTime"];
            Author = (string)dicts["Author"];
            ArticalContent = (string)dicts["ArticalContent"];
            PaperID =  (int)dicts["PaperID"];
            ClassID =  (string)dicts["ClassID"];
        }
     public IDictionary<string, object> DictSerialize()
        {
            var dict = new Dictionary<string, object>
                {
                    { "Title", this.Title },
                    { "PublishTime", this.PublishTime.Value },
                    { "Author", this.Author },
                    { "ArticalContent", this.ArticalContent },
                    { "PaperID", this.PaperID.Value },
                    { "ClassID", this.ClassID }
                };
            return dict;
        }

       它指示了最經常使用的使用場景。但是讀者可能會注意到如下問題:ui

       Title = (string)dicts["Title"]; this

      (1) 若是字典中沒有對應的屬性項,那麼經過索引器讀這個屬性是會報錯的。spa

      (2) 即便有對應的屬性項,可它的值是Null,那麼它可能覆蓋掉本來是正常的屬性值。

      (3) 類型不統一:這個字典多是由JSON序列化器提供的,或是由某數據庫的驅動提供的,那麼,一個表示時間的字段,傳過來的類型多是DateTime,也多是string,如何不用醜陋的代碼作複雜的判斷和轉換呢?

       對此,咱們添加了一個IDictionary的擴展方法:

public static T Set<T>(this IDictionary<string, object> dict, string key, T oldValue)
        {
            object data = null;
            if (dict.TryGetValue(key, out data))
            {
                Type type = typeof(T);
                if (type.IsEnum)
                {
                    var index = (T)Convert.ChangeType(data, typeof(int));
                    return (index);
                }
                if (data == null)
                {
                    return oldValue;
                }
                var value = (T)Convert.ChangeType(data, typeof(T));
                return value;
            }

            return oldValue;
        }

 

     因而,從字典中讀取數據(字典反序列化),就能夠變得像下面這樣優雅了, 因爲編譯器的自動類型推斷功能,連T都不用寫了。若是數據獲取失敗,原有值不受影響。

 public void DictDeserialize(IDictionary<string, object> dicts, Scenario scenario = Scenario.Database)
        {
            this.Title = dicts.Set("Title", this.Title);
            this.PublishTime = dicts.Set("PublishTime", this.PublishTime);
            this.Author = dicts.Set("Author", this.Author);
            this.ArticalContent = dicts.Set("ArticalContent", this.ArticalContent);
            this.PaperID = dicts.Set("PaperID", this.PaperID);
            this.ClassID = dicts.Set("ClassID", this.ClassID);  
        }

     但是,又會有人問,若是某屬性的類型是List<T>,好比是string的數組呢?

     這個問題會有點複雜,如何保存一個List<string>呢?若是是MongoDB,它的驅動是能直接存儲/返回List<string>的,但若是是文件存儲,通常會存儲成JSON格式,例如["a","b","c","d"]這樣的格式,所以咱們能夠定義以下的擴展方法:

        public static List<T> SetArray<T>(this IDictionary<string, object> dict, string key, List<T> oldValue)
           
        {
            object data = null;

            if (dict.TryGetValue(key, out data))
            {
                var list = data as List<T>;
                if (list != null) return list;
                string str = data.ToString();
                if (str=="[]")
                    return oldValue;
                if (str[0] != '[') return oldValue;
                return JsonConvert.Import<List<T>>(str);
            }
            return oldValue.ToList();
        }

       這裏爲了轉換JSON風格的string,用了第三方的JSON轉換庫:Jayrock.Json.  若是你本身有數組和string的轉換方法,不妨能夠寫新的擴展方法。

       經過該接口,可方便的實現對象間的數據拷貝:

    public T Copy<T>(T oldValue) where T : IDictionarySerializable, new()
        {
            IDictionary<string, object> data = oldValue.DictSerialize();
            var newValue = new T();
            newValue.DictDeserialize(data);
            return newValue;
        }

 

  三. 如何作數據庫接口層?

        有了這樣的接口,咱們就能夠方便的作一個數據接口層,隔離數據庫和真實類型了,下面依舊以MongoDB爲例。若是咱們須要獲取表內全部的對象,可用以下的方法:

  /// <summary>
        /// 獲取數據庫中的全部實體
        /// </summary>
        /// <returns></returns>
        public List<T> GetAllEntitys<T>(string tableName) where T : IDictionarySerializable, new()
        {
            if (this.IsUseable == false)
            {
                return new List<T>();
            }

            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            var documents = collection.FindAll().Documents.ToList();
            var list = new List<T>();
            foreach (Document document in documents)
            {
                var user = new T();
                user.DictDeserialize(document);
                list.Add(user);
            }
            return list;
        }

       若是想更新或保存一個文檔,能夠用以下的代碼:

  /// <summary>
        /// 更新或增長一個新文檔
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="tableName">表名 </param>
        /// <param name="keyName"> </param>
        /// <param name="keyvalue"> </param>
        public void SaveOrUpdateEntity(IDictionarySerializable entity, string tableName, string keyName, object keyvalue)
        {
            if (this.IsUseable == false)
            {
                return;
            }
            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            Document document = collection.FindOne(new Document { { keyName, keyvalue } });
            if (document != null)
            {
                this.UpdateDocument(entity, document);
                collection.Save(document);
            }
            else
            {
                Document doc = this.GetNewDocument(entity);
                collection.Save(doc);
            }
        }

  
 private Document GetNewDocument(IDictionarySerializable entity)
        {
            IDictionary<string, object> datas = entity.DictSerialize();

            var document = new Document(datas);

            return document;
        }

        private void UpdateDocument(IDictionarySerializable data, Document document)
        {
            IDictionary<string, object> datas = data.DictSerialize();
            foreach (string key in datas.Keys)
            {
                document[key] = datas[key];
            }
        }
SaveOrUpdateCode

      能夠經過泛型或者接口的方法,方便的讀取/存儲這些數據。

      完整的驅動層代碼,在這裏:

 

  /// <summary>
    /// Mongo數據庫服務
    /// </summary>
    public class MongoServiceBase
    {
        #region Constants and Fields

        protected IMongoDatabase DB;

        protected Mongo Mongo;

        private Document update;

        #endregion

        //連接字符串 

        #region Properties

        public string ConnectionString { get; set; }

        //數據庫名 
        public string DBName { get; set; }

        public bool IsUseable { get; set; }

        /// <summary>
        /// 本地數據庫位置
        /// </summary>
        public string LocalDBLocation { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// 鏈接到數據庫,只需執行一次
        /// </summary>
        public virtual bool ConnectDB()
        {
            var config = new MongoConfigurationBuilder();

            config.ConnectionString(this.ConnectionString);
            //定義Mongo服務 

            this.Mongo = new Mongo(config.BuildConfiguration());

            if (this.Mongo.TryConnect())
            {
                this.IsUseable = true;
                this.update = new Document();

                this.update["$inc"] = new Document("id", 1);

                this.DB = this.Mongo.GetDatabase(this.DBName);
            }
            else
            {
                this.IsUseable = false;
            }
            return this.IsUseable;
        }

        public T Copy<T>(T oldValue) where T : IDictionarySerializable, new()
        {
            IDictionary<string, object> data = oldValue.DictSerialize();
            var newValue = new T();
            newValue.DictDeserialize(data);
            return newValue;
        }

        /// <summary>
        /// 建立一個自增主鍵索引表
        /// </summary>
        /// <param name="tableName">表名</param>
        public void CreateIndexTable(string tableName)
        {
            if (this.IsUseable == false)
            {
                return;
            }
            IMongoCollection idManager = this.DB.GetCollection("ids");
            Document idDoc = idManager.FindOne(new Document("Name", tableName));
            if (idDoc == null)
            {
                idDoc = new Document();
                idDoc["Name"] = tableName;
                idDoc["id"] = 0;
            }

            idManager.Save(idDoc);
        }

        /// <summary>
        /// 獲取數據庫中的全部實體
        /// </summary>
        /// <returns></returns>
        public List<T> GetAllEntitys<T>(string tableName) where T : IDictionarySerializable, new()
        {
            if (this.IsUseable == false)
            {
                return new List<T>();
            }

            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            List<Document> documents = collection.FindAll().Documents.ToList();
            var list = new List<T>();
            foreach (Document document in documents)
            {
                var user = new T();
                user.DictDeserialize(document);
                list.Add(user);
            }
            return list;
        }

        /// <summary>
        /// 獲取必定範圍的實體
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="type"></param>
        /// <param name="mount"></param>
        /// <param name="skip"></param>
        /// <returns></returns>
        public List<IDictionarySerializable> GetAllEntitys(string tableName, Type type)
        {
            if (this.IsUseable == false)
            {
                return new List<IDictionarySerializable>();
            }

            List<Document> docuemts = this.DB.GetCollection<Document>(tableName).FindAll().Documents.ToList();
            var items = new List<IDictionarySerializable>();
            foreach (Document document in docuemts)
            {
                object data = Activator.CreateInstance(type);
                var suck = (IDictionarySerializable)data;
                suck.DictDeserialize(document);
                items.Add(suck);
            }
            return items;
        }

        /// <summary>
        /// 獲取必定範圍的實體
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="type"></param>
        /// <param name="mount"></param>
        /// <param name="skip"></param>
        /// <returns></returns>
        public List<IDictionarySerializable> GetEntitys(string tableName, Type type, int mount, int skip)
        {
            if (this.IsUseable == false)
            {
                return new List<IDictionarySerializable>();
            }

            List<Document> docuemts =
                this.DB.GetCollection<Document>(tableName).FindAll().Skip(skip).Limit(mount).Documents.ToList();
            var items = new List<IDictionarySerializable>();
            foreach (Document document in docuemts)
            {
                object data = Activator.CreateInstance(type);
                var suck = (IDictionarySerializable)data;
                suck.DictDeserialize(document);
                items.Add(suck);
            }
            return items;
        }

        public List<T> GetEntitys<T>(string tableName, int mount, int skip) where T : IDictionarySerializable, new()
        {
            if (this.IsUseable == false)
            {
                return new List<T>();
            }

            ICursor<Document> collection = this.DB.GetCollection<Document>(tableName).Find(null).Skip(skip).Limit(mount);

            var users = new List<T>();
            foreach (Document document in collection.Documents)
            {
                var user = new T();
                user.DictDeserialize(document);
                users.Add(user);
            }
            return users;
        }

        /// <summary>
        /// 直接插入一個實體
        /// </summary>
        /// <param name="user"></param>
        /// <param name="tableName"></param>
        public bool InsertEntity(IDictionarySerializable user, string tableName, string key, out int index)
        {
            if (this.IsUseable == false)
            {
                index = 0;
                return false;
            }

            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            IMongoCollection idManager = this.DB.GetCollection("ids");
            Document docID = idManager.FindAndModify(this.update, new Document("Name", tableName), returnNew: true);

            //下面三句存入數據庫
            Document doc = this.GetNewDocument(user);

            doc[key] = docID["id"];
            index = (int)docID["id"];
            ;
            collection.Save(doc);
            return true;
        }

        public void RepairDatabase()
        {
            bool local = (this.ConnectionString.Contains("localhost") || this.ConnectionString.Contains("127.0.0.1"));
            if (local == false)
            {
                throw new Exception("MongoDB數據庫不在本地,沒法啓動自動數據庫修復");
            }

            var mydir = new DirectoryInfo(this.LocalDBLocation);
            FileInfo file = mydir.GetFiles().FirstOrDefault(d => d.Name == "mongod.lock");
            if (file == null)
            {
                throw new Exception("修復失敗,您是否沒有安裝MongoDB數據庫");
            }
            try
            {
                File.Delete(file.FullName);
                string str = CMDHelper.Execute("net start MongoDB");
            }
            catch (Exception ex)
            {
            }
        }

        /// <summary>
        /// 更新或增長一個新文檔
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="tableName">表名 </param>
        /// <param name="keyName"> </param>
        /// <param name="keyvalue"> </param>
        public void SaveOrUpdateEntity(
            IDictionarySerializable entity, string tableName, string keyName, object keyvalue)
        {
            if (this.IsUseable == false)
            {
                return;
            }
            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            Document document = collection.FindOne(new Document { { keyName, keyvalue } });
            if (document != null)
            {
                this.UpdateDocument(entity, document);
                collection.Save(document);
            }
            else
            {
                Document doc = this.GetNewDocument(entity);
                collection.Save(doc);
            }
        }

        public bool TryFindEntity<T>(string tableName, string keyName, object keyvalue, out T result)
            where T : class, IDictionarySerializable, new()
        {
            IMongoCollection<Document> collection = this.DB.GetCollection<Document>(tableName);
            Document document = collection.FindOne(new Document { { keyName, keyvalue } });
            if (document == null)
            {
                result = null;
                return false;
            }
            result = new T();
            try
            {
                result.DictDeserialize(document);
            }
            catch (Exception ex)
            {
                XLogSys.Print.Error(ex);
            }

            return true;
        }

        #endregion

        #region Methods

        private Document GetNewDocument(IDictionarySerializable entity)
        {
            IDictionary<string, object> datas = entity.DictSerialize();

            var document = new Document(datas);

            return document;
        }

        private void UpdateDocument(IDictionarySerializable data, Document document)
        {
            IDictionary<string, object> datas = data.DictSerialize();
            foreach (string key in datas.Keys)
            {
                document[key] = datas[key];
            }
        }

        #endregion
    }
完整的驅動層代碼

 

四.一些問題

      下面咱們討論一些遇到的問題:

  •     爲何不用反射來讀寫字段?而非要顯式的實現這兩個接口呢?

          性能是首先要考慮的,而實現這兩個接口意味着更多的控制權和靈活性。 另外,對於不少鍵值對的數據庫來講,「Key」也是要佔用存儲空間的,並且佔用很多。若是實體類中字段屬性特別長,那麼就會佔用可觀的存儲,MongoDB就是如此。所以,在讀寫數據庫的場景中,應當保持Key較短。 

  •     IDictionary<string, object> 的object是否是會有性能影響?

          因爲保存的是String-object的鍵值對形式,所以不可避免的存在裝箱和拆箱操做,在數據量大時,性能損耗確定仍是有的。不過,大部分數據庫驅動不也有大量這種操做麼?畢竟只須要讀寫一次便可,性能損失可忽略不計,再說,還有更好的方法麼?

  •     是否能經過該接口實現LINQ查詢甚至SQL查詢?

      應該是能夠的,它創建了屬性與名稱的映射關係,所以能夠經過該接口實現LINQ和SQL解析器,實現查詢等功能。

  •     爲何不用官方的ISerializable接口呢?

      緣由如第一條,依靠attribute的方法沒有靈活性,同時,對二進制序列化等操做,實現起來也比較困難。

五. 其餘應用場景

         除了上面介紹的應用場景以外,該接口還能用於以下用途:

  •   環境和模塊配置: 能夠方便的將環境中全部的配置以鍵值對的方式存儲,並在須要的時候加載
  •   RPC: 因爲實現了到XML/JSON的方便轉換,能夠很容易的實現遠程過程調用
  •   Word文檔導出:該接口包含了鍵值對,所以在Word模板中,能夠寫上Key, 經過該接口,一次性所有替換便可,很是方便。
  •   界面綁定: 經過該接口實現動態的Binding語法。

      一個方便的東西,應該是簡單的。經過該接口得到的好處很是多,還須要你們去挖掘。值得提出的是,它也能應用在其餘語言上,好比JAVA, 用HashMap<String,Object>來實現相似的需求。

       若是有任何問題,歡迎討論!

相關文章
相關標籤/搜索