你是否和我同样有以下的困扰:程序员
你们确定都遇到过和我类似的困扰,为了存储或读取某个对象(好比从文件或数据库里读取),你不得不写下大量数据类型转换和赋值语句,并且在程序中不能复用,这样的代码处处都是,真是代码的臭味。 数据库
因为键值对的概念已经深刻人心,因而便有了这样一个叫作“字典序列化”的接口,该接口不是官方定义的,而是我根据实际需求总结的,同时我发现,它真的很是好用。数组
废话不说,先看该接口的定义:框架
/// <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]; } }
能够经过泛型或者接口的方法,方便的读取/存储这些数据。
完整的驱动层代码,在这里:
/// <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较短。
因为保存的是String-object的键值对形式,所以不可避免的存在装箱和拆箱操做,在数据量大时,性能损耗确定仍是有的。不过,大部分数据库驱动不也有大量这种操做么?毕竟只须要读写一次便可,性能损失可忽略不计,再说,还有更好的方法么?
应该是能够的,它创建了属性与名称的映射关系,所以能够经过该接口实现LINQ和SQL解析器,实现查询等功能。
缘由如第一条,依靠attribute的方法没有灵活性,同时,对二进制序列化等操做,实现起来也比较困难。
除了上面介绍的应用场景以外,该接口还能用于以下用途:
一个方便的东西,应该是简单的。经过该接口得到的好处很是多,还须要你们去挖掘。值得提出的是,它也能应用在其余语言上,好比JAVA, 用HashMap<String,Object>来实现相似的需求。
若是有任何问题,欢迎讨论!