一. 我们的需求

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

  •      你需要将一个类转换为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];
            }
        }
SaveOrUpdateCode

相关文章:

  • 2021-12-16
  • 2021-09-22
  • 2021-05-31
  • 2022-01-13
  • 2022-12-23
  • 2021-12-28
  • 2021-06-29
猜你喜欢
  • 2022-12-23
  • 2021-08-25
  • 2022-12-23
  • 2019-08-03
  • 2021-12-17
  • 2021-09-05
  • 2022-12-23
相关资源
相似解决方案