博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
推荐一个简单好用的接口——字典序列化
阅读量:6315 次
发布时间:2019-06-22

本文共 14816 字,大约阅读时间需要 49 分钟。

一. 我们的需求

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

  •      你需要将一个类转换为XML或JSON存储或传输,但总有你不想存储或想特殊处理的字段,用序列化器自身的反射功能就看起来颇为鸡肋了。
  •      与MongoDB等键值对数据库做交互,很多ORM框架都无效了,如何写一个通用的数据接口层?而且不用去添加丑陋的"MongoIgnore"这样的attribute?
  •      你要将一个对象的属性“拷贝”到另外一个对象,怎么做?C语言的拷贝构造函数?那太原始了。
  •      界面绑定:总有一些绑定表达式,想通过动态的形式提交给框架,而不是写死在xaml里,那是否又得在C#里写一堆对象映射的代码了?

     大家肯定都遇到过和我相似的困扰,为了存储或读取某个对象(比如从文件或数据库里读取),你不得不写下大量数据类型转换和赋值语句,而且在程序中不能复用,这样的代码到处都是,真是代码的臭味。 

     由于键值对的概念已经深入人心,于是便有了这样一个叫做“字典序列化”的接口,该接口不是官方定义的,而是我根据实际需求总结的,同时我发现,它真的非常好用。

     废话不说,先看该接口的定义:

复制代码
///     /// 进行字典序列化的接口,方便实现键值对的映射关系和重建    ///     public interface IDictionarySerializable    {        ///         /// 从数据到字典        ///         /// 
IDictionary
DictSerialize(Scenario scenario = Scenario.Database); ///
/// 从字典到数据 /// ///
字典 ///
应用场景 void DictDeserialize(IDictionary
dicts, Scenario scenario = Scenario.Database); }
复制代码

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

     值得注意的是,函数参数中有一个Scenario枚举,该枚举指定了转换的场景,例如数据库和用户界面,在转换时可能就有一定的区别,程序员可通过实际情况来确定,具体原因可以看下边的部分。你可以传入更多的参数,指示该接口的独特序列化方法。

二. 如何使用?

      先定义一个实体类:Artical, 它是一个简单的POCO类型, 包含Title等六个属性,为了节约篇幅,就不在此声明它了。我们先看如下的代码:

复制代码
//define a entity named Artical with following propertynames   public void DictDeserialize(IDictionary
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
DictSerialize() { var dict = new Dictionary
{ { "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
(this IDictionary
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
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
SetArray
(this IDictionary
dict, string key, List
oldValue) { object data = null; if (dict.TryGetValue(key, out data)) { var list = data as List
; if (list != null) return list; string str = data.ToString(); if (str=="[]") return oldValue; if (str[0] != '[') return oldValue; return JsonConvert.Import
>(str); } return oldValue.ToList(); }
复制代码

       这里为了转换JSON风格的string,用了第三方的JSON转换库:Jayrock.Json.  如果你自己有数组和string的转换方法,不妨可以写新的扩展方法。

       通过该接口,可方便的实现对象间的数据拷贝:

复制代码
public T Copy
(T oldValue) where T : IDictionarySerializable, new() { IDictionary
data = oldValue.DictSerialize(); var newValue = new T(); newValue.DictDeserialize(data); return newValue; }
复制代码

 

  三. 如何做数据库接口层?

        有了这样的接口,我们就可以方便的做一个数据接口层,隔离数据库和真实类型了,下面依旧以MongoDB为例。如果我们需要获取表内所有的对象,可用如下的方法:

复制代码
///         /// 获取数据库中的所有实体        ///         /// 
public List
GetAllEntitys
(string tableName) where T : IDictionarySerializable, new() { if (this.IsUseable == false) { return new List
(); } IMongoCollection
collection = this.DB.GetCollection
(tableName); var documents = collection.FindAll().Documents.ToList(); var list = new List
(); foreach (Document document in documents) { var user = new T(); user.DictDeserialize(document); list.Add(user); } return list; }
复制代码

       如果想更新或保存一个文档,可以用如下的代码:

///         /// 更新或增加一个新文档        ///         ///         /// 表名         ///          ///          public void SaveOrUpdateEntity(IDictionarySerializable entity, string tableName, string keyName, object keyvalue)        {            if (this.IsUseable == false)            {                return;            }            IMongoCollection
collection = this.DB.GetCollection
(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
datas = entity.DictSerialize(); var document = new Document(datas); return document; } private void UpdateDocument(IDictionarySerializable data, Document document) { IDictionary
datas = data.DictSerialize(); foreach (string key in datas.Keys) { document[key] = datas[key]; } }
SaveOrUpdateCode

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

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

 

///     /// Mongo数据库服务    ///     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; }        ///         /// 本地数据库位置        ///         public string LocalDBLocation { get; set; }        #endregion        #region Public Methods        ///         /// 连接到数据库,只需执行一次        ///         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 oldValue) where T : IDictionarySerializable, new() { IDictionary
data = oldValue.DictSerialize(); var newValue = new T(); newValue.DictDeserialize(data); return newValue; } ///
/// 创建一个自增主键索引表 /// ///
表名 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); } ///
/// 获取数据库中的所有实体 /// ///
public List
GetAllEntitys
(string tableName) where T : IDictionarySerializable, new() { if (this.IsUseable == false) { return new List
(); } IMongoCollection
collection = this.DB.GetCollection
(tableName); List
documents = collection.FindAll().Documents.ToList(); var list = new List
(); foreach (Document document in documents) { var user = new T(); user.DictDeserialize(document); list.Add(user); } return list; } ///
/// 获取一定范围的实体 /// ///
///
///
///
///
public List
GetAllEntitys(string tableName, Type type) { if (this.IsUseable == false) { return new List
(); } List
docuemts = this.DB.GetCollection
(tableName).FindAll().Documents.ToList(); var items = new List
(); foreach (Document document in docuemts) { object data = Activator.CreateInstance(type); var suck = (IDictionarySerializable)data; suck.DictDeserialize(document); items.Add(suck); } return items; } ///
/// 获取一定范围的实体 /// ///
///
///
///
///
public List
GetEntitys(string tableName, Type type, int mount, int skip) { if (this.IsUseable == false) { return new List
(); } List
docuemts = this.DB.GetCollection
(tableName).FindAll().Skip(skip).Limit(mount).Documents.ToList(); var items = new List
(); foreach (Document document in docuemts) { object data = Activator.CreateInstance(type); var suck = (IDictionarySerializable)data; suck.DictDeserialize(document); items.Add(suck); } return items; } public List
GetEntitys
(string tableName, int mount, int skip) where T : IDictionarySerializable, new() { if (this.IsUseable == false) { return new List
(); } ICursor
collection = this.DB.GetCollection
(tableName).Find(null).Skip(skip).Limit(mount); var users = new List
(); foreach (Document document in collection.Documents) { var user = new T(); user.DictDeserialize(document); users.Add(user); } return users; } ///
/// 直接插入一个实体 /// ///
///
public bool InsertEntity(IDictionarySerializable user, string tableName, string key, out int index) { if (this.IsUseable == false) { index = 0; return false; } IMongoCollection
collection = this.DB.GetCollection
(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) { } } ///
/// 更新或增加一个新文档 /// ///
///
表名 ///
///
public void SaveOrUpdateEntity( IDictionarySerializable entity, string tableName, string keyName, object keyvalue) { if (this.IsUseable == false) { return; } IMongoCollection
collection = this.DB.GetCollection
(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
(string tableName, string keyName, object keyvalue, out T result) where T : class, IDictionarySerializable, new() { IMongoCollection
collection = this.DB.GetCollection
(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
datas = entity.DictSerialize(); var document = new Document(datas); return document; } private void UpdateDocument(IDictionarySerializable data, Document document) { IDictionary
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>来实现类似的需求。

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

   

 

    

 

 

 

 

 

    

    

作者:
出处:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 本文转自FerventDesert博客园博客,原文链接:http://www.cnblogs.com/buptzym/p/3297979.html,如需转载请自行联系原作者

你可能感兴趣的文章
android的onCreateOptionsMenu()创建菜单Menu详解
查看>>
js swipe 图片滑动控件实现 任意尺寸适用任意屏幕
查看>>
截取字符串替换成星号
查看>>
Kinect for Windows V2.0 新功能
查看>>
解析:使用easyui的form提交表单,在IE下出现类似附件下载时提示是否保存的现象...
查看>>
PHP 错误与异常 笔记与总结(17 )像处理异常一样处理 PHP 错误
查看>>
算法-数组中重复的数字
查看>>
Linux下samba的安装与配置
查看>>
分析Cocos2d-x横版ACT手游源 1、登录
查看>>
SVG在网页中的四种使用方式
查看>>
Unity多玩家网络游戏开发教程1章Unity带有网络功能
查看>>
PEM (Privacy Enhanced Mail) Encoding
查看>>
100种不同图片切换效果插件pageSwitch
查看>>
Android EditText setOnClickListener事件 只有获取焦点才能响应 采用setOnTouchListener解决...
查看>>
Redis入门很简单之六【Jedis常见操作】
查看>>
Java中的Enum的使用与分析
查看>>
WebView之2
查看>>
34 网络相关函数(二)——live555源码阅读(四)网络
查看>>
pdo,更高的sql安全性
查看>>
ckplayer.js视频播放插件
查看>>