• 字典序列化


    字典序列化

    一. 我们的需求

         你是否和我一样有如下的困扰:

    •      你需要将一个类转换为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);
        }  
    复制代码

         它只有两个方法,一个是从字典中读取数据到实体类,另一个是将实体类的数据写入字典。你要做的,就是让你的实体类实现这个接口。

         值得注意的是,函数参数中有一个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;
            }
    复制代码

           它指示了最常用的使用场景。可是读者可能会注意到以下问题:

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

          (1) 如果字典中没有对应的属性项,那么通过索引器读这个属性是会报错的。

          (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];
                }
            }
    复制代码

          可以通过泛型或者接口的方法,方便的读取/存储这些数据。

          完整的驱动层代码,在这里:

    复制代码
      /// <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<stringobject> 的object是不是会有性能影响?

              由于保存的是String-object的键值对形式,因此不可避免的存在装箱和拆箱操作,在数据量大时,性能损耗肯定还是有的。不过,大部分数据库驱动不也有大量这种操作么?毕竟只需要读写一次即可,性能损失可忽略不计,再说,还有更好的方法么?

    •     是否能通过该接口实现LINQ查询甚至SQL查询?

          应该是可以的,它建立了属性与名称的映射关系,因此可以通过该接口实现LINQ和SQL解析器,实现查询等功能。

    •     为什么不用官方的ISerializable接口呢?

          原因如第一条,依靠attribute的方法没有灵活性,同时,对二进制序列化等操作,实现起来也比较困难。

    五. 其他应用场景

             除了上面介绍的应用场景之外,该接口还能用于如下用途:

    •   环境和模块配置: 可以方便的将环境中所有的配置以键值对的方式存储,并在需要的时候加载
    •   RPC: 由于实现了到XML/JSON的方便转换,可以很容易的实现远程过程调用
    •   Word文档导出:该接口包含了键值对,因此在Word模板中,可以写上Key, 通过该接口,一次性全部替换即可,非常方便。
    •   界面绑定: 通过该接口实现动态的Binding语法。

          一个方便的东西,应该是简单的。通过该接口获得的好处非常多,还需要大家去挖掘。值得提出的是,它也能应用在其他语言上,比如JAVA, 用HashMap<String,Object>来实现类似的需求。

           如果有任何问题,欢迎讨论!

       

  • 相关阅读:
    移动端常用单位——rem
    媒体查询media query
    canvas——绘制解锁图案
    canvas——离屏
    canvas——动画实例
    canvas绘制圆弧
    canvas二次贝塞尔&三次贝塞尔操作实例
    form表单中id与name的区别
    数据存储 mysql
    数据存储 csv
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3299239.html
Copyright © 2020-2023  润新知