在日常的软件开发和使用过程中,我们发现同一套系统的同一配置项在不同的客户环境中是存在各种各样的差异的。在差异较为分散时,如何较好的管理这些差异,使得维护过程能够更加安全和快速,一直在这样那样的困扰着开发者和维护者。

例如,有系统中需要配置日志的记录路径和日志文件的命名方式。默认的日志是放在C盘目录下并以Log_XXX.txt进行命名。

<?xml version=""1.0"" encoding=""utf-8""?>
<LogSetting>
<Path>C:\</Path>
<FileName>Log_{0}.txt</FileName>
</LogSetting>

但是在部署至客户时由于不同客户的需求需要对配置进行更改,但是不同的客户可能只改动其中的一部分。A客户希望将日志文件放在Z盘。B客户希望日志文件可以有自定义的命名开头如ZZZ_Log_XXX.txt。

这是我们就希望存在默认设置的配置文档的同时,又会存在一份差异内容的配置文档(以下称为增量文档),通过将两份配置文档的内容进行合并后产生不同客户的配置设定。如下:

<!--客户A的附加代码-->
<?xml version=""1.0"" encoding=""utf-8""?>
<LogSetting>
<Path>Z:\</Path>
</LogSetting>
<!--客户B的附加代码-->
<?xml version=""1.0"" encoding=""utf-8""?>
<LogSetting>
<FileName>{1}_Log_{0}.txt</FileName>
</LogSetting>

在部署给不同需求的客户时,只需要将原始配置+不同客户的附加代码同时进行部署,即可实现不同客户间的不同定制配置。

通过如此配置,既可以实现相同内容的公用及增量内容的独立部署,也可以帮助配置管理人员方便的了解客户定制配置的内容。

在这样的背景下,我开发了 XPatchLib 对象增量数据序列化及反序列化器

通过XPatchLib,可以实现原始对象实例与变更后对象实例间增量内容的序列化(记录增量内容为XML格式)及反序列化(将XML格式的增量内容合并至原始对象实例,使之与变更后对象实例间值相等或引用相等)

Nuget:

https://www.nuget.org/packages/XPatchLib/

Install-Package XPatchLib

帮助文件:https://guqiangjs.github.io/XPatchLib.Net.Doc/


基本用法和输出格式

让我们看一个很简单的例子。开始,我们定义了一个简单的CreditCard类:

public class CreditCard
{
public string CardExpiration { get; set; } public string CardNumber { get; set; } public override bool Equals(object obj)
{
CreditCard card = obj as CreditCard;
if (card == null)
{
return false;
}
return string.Equals(this.CardNumber, card.CardNumber)
&& string.Equals(this.CardExpiration, card.CardExpiration);
}
}

以下代码说明如何在两个不同内容之间的CreditCard实例间记录变化信息(增量内容)

首先创建两个类型相同,但内容不同的CreditCard对象。

CreditCard card1 = new CreditCard()
{
CardExpiration = "05/12",
CardNumber = ""
};
CreditCard card2 = new CreditCard()
{
CardExpiration = "05/17",
CardNumber = ""
};

调用XPatchSerializer对两个对象的增量内容进行序列化

XPatchSerializer serializer = new XPatchSerializer(typeof(CreditCard));

string context = string.Empty;
using (MemoryStream stream = new MemoryStream())
{
serializer.Divide(stream, card1, card2);
stream.Position = ;
using (StreamReader stremReader = new StreamReader(stream, Encoding.UTF8))
{
context = stremReader.ReadToEnd();
}
}

经过执行以上代码,context的内容将为:

<?xml version=""1.0"" encoding=""utf-8""?>
<CreditCard>
<CardExpiration>05/17</CardExpiration>
<CardNumber>9876543210</CardNumber>
</CreditCard>

通过以上代码,我们实现了两个同类型的对象实例间,增量的序列化。记录了两个对象之间增量的内容。

下面将介绍如何将已序列化的增量内容附加回原始对象实例,使其与修改后的对象实例形成两个值相同的对象实例。

CreditCard card3 = null;
XPatchSerializer serializer = new XPatchSerializer(typeof(CreditCard));
using (StringReader reader = new StringReader(changedContext))
{
card3 = serializer.Combine(reader, card1) as CreditCard;
}

经过以上代码,可以使新增的 card3 实例的 CardExpiration 属性的值由card1实例中的 "05/12" 变更为增量内容中记录的 "05/17",CardNumber的值也由card1实例中的"0123456789"变更为了增量内容中记录的"9876543210"。如果使用值比较的方式比较 card3 和 card2 两个实例,会发现这两个实例完全相同。

操作简单类型集合

简单类型的集合

public class Warehouse
{
public string Name { get; set; } public string Address { get; set; } public string[] Items { get; set; }
}

之后创建两个不同内容的Warehouse对象实例。

Warehouse w1 = new Warehouse()
{
Name = "Company A",
Items = new string[] { "ItemA", "ItemB", "ItemC" }
}; Warehouse w2 = new Warehouse()
{
Name = "Company B",
Items = new string[] { "ItemA", "ItemC", "ItemD" }
};

增量内容结果为:

<?xml version="1.0" encoding="utf-8"?>
<Warehouse>
<Items>
<String Action="Remove">ItemB</String>
<String Action="Add">ItemD</String>
</Items>
<Name>Company B</Name>
</Warehouse>

以上内容表示了,w1与w2之间的增量为Name属性被变更为Company B,同时Items内容集合的ItemB被移除,并增加了ItemD。

那么如果我们定义了w3,将Items设为null会如何?

Warehouse w2 = new Warehouse()
{
Name = "Company B",
Items = null
};

结果如下:

<?xml version="1.0" encoding="utf-8"?>
<Warehouse>
<Items Action="SetNull" />
<Name>Company B</Name>
</Warehouse>

操作复杂类型

为说明复杂类型,我们创建了一个订单类型(OrderInfo),其中包含了自定义的地址信息类型(AddressInfo)

public class AddressInfo
{
public string Country { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Zip { get; set; }
public string City { get; set; }
public string Address { get; set; }
} public class OrderInfo
{
public int OrderId { get; set; }
public decimal OrderTotal { get; set; }
public AddressInfo ShippingAddress { get; set; }
public string UserId { get; set; }
public DateTime Date { get; set; }
}

同样,我们创建两个不同内容的OrderInfo类型的实例

OrderInfo order1 = new OrderInfo(){
OrderId = ,
OrderTotal = 200.45m,
ShippingAddress = new AddressInfo(){
Country = "China",
Name = "Customer",
Phone = "138-1234-5678",
Zip = "",
City = "Beijing",
Address = "",
},
UserId = "",
Date = new DateTime(,,)
}; OrderInfo order2 = new OrderInfo(){
OrderId = ,
OrderTotal = 180.50m,
ShippingAddress = new AddressInfo(){
Country = "China",
Name = "Customer",
Phone = "138-1234-5678",
Zip = "",
City = "Shanghai",
Address = "",
},
UserId = "",
Date = new DateTime(,,)
};

增量内容为:

<?xml version="1.0" encoding="utf-8"?>
<OrderInfo>
<Date>2010-04-30T00:00:00</Date>
<OrderId>2</OrderId>
<OrderTotal>180.50</OrderTotal>
<ShippingAddress>
<City>Shanghai</City>
</ShippingAddress>
</OrderInfo>

操作复杂类型集合

操作复杂类型的集合时,系统通过定义的主键(PrimaryKey)信息来比对集合中的元素是否相同。

用在集合中的复杂类型均需要指定PrimaryKeyAttribute,否则在处理过程中会引发AttributeMissException异常。(也可以通过调用RegisterTypes方法来注册类型的主键)

同时PrimaryKeyAttribute中定义的主键 只能是值类型数据,否则在处理过程中会引发PrimaryKeyException异常。

[PrimaryKey("OrderId")]
public class OrderInfo
{
public int OrderId { get; set; }
public decimal OrderTotal { get; set; }
public AddressInfo ShippingAddress { get; set; }
public string UserId { get; set; }
public DateTime Date { get; set; }
} public class OrderList
{
public List<OrderInfo> Orders { get; set; }
public string UserId { get; set; }
}

以上代码标记了OrderInfo类型对象的PrimaryKey是OrderInfo属性,在进行增量内容查找的过程中,会通过该属性的值在集合中的元素间进行查找。

同样,我们创建两个不同内容的OrderList类型的实例。

OrderList list1 = new OrderList(){
UserId = "",
Orders = new List<OrderInfo>(){
new OrderInfo(){
OrderId = ,
OrderTotal = 200.45m,
UserId = "",
Date = new DateTime(,,)
},
new OrderInfo(){
OrderId = ,
OrderTotal = 450.23m,
UserId = "",
Date = new DateTime(,,)
},
new OrderInfo(){
OrderId = ,
OrderTotal = 185.60m,
UserId = "",
Date = new DateTime(,,)
}
}
}; OrderList list2 = new OrderList(){
UserId = "",
Orders = new List<OrderInfo>(){
new OrderInfo(){
OrderId = ,
OrderTotal = 200.45m,
UserId = "",
Date = new DateTime(,,)
},
new OrderInfo(){
OrderId = ,
OrderTotal = 230.89m,
UserId = "",
Date = new DateTime(,,)
},
new OrderInfo(){
OrderId = ,
OrderTotal = 67.30m,
UserId = "",
Date = new DateTime(,,)
}
}
};

增量内容为:

<?xml version="1.0" encoding="utf-8"?>
<OrderList>
<Orders>
<OrderInfo Action="Remove" OrderId="3" />
<OrderInfo OrderId="2">
<OrderTotal>230.89</OrderTotal>
</OrderInfo>
<OrderInfo Action="Add">
<Date>2008-08-08T00:00:00</Date>
<OrderId>4</OrderId>
<OrderTotal>67.30</OrderTotal>
<UserId>1234</UserId>
</OrderInfo>
</Orders>
</OrderList>

RegisterTypes方法

在无法修改类型定义,为其增加或修改 PrimaryKeyAttribute 的情况下,可以在调用 Divide 或 Combine 方法前,调用此方法,传入需要修改的Type及与其对应的主键名称集合。

XPatchSerializer在处理时会按照传入的设置进行处理。

下面的示例使用 RegisterTypes 方法向 XPatchSerializer 注册待处理的类型的主键信息 。

首先有如下的类型定义OrderedItem,该类型并未标记PrimaryKeyAttribute,所以在正常处理集合类型时会抛出AttributeMissException异常。

public class OrderedItem
{
public string Description;
public string ItemName;
public decimal LineTotal;
public int Quantity;
public decimal UnitPrice; public void Calculate()
{
LineTotal = UnitPrice * Quantity;
}
}

为规避此问题,我们可以在XPatchSerializer初始化之后,调用RegisterTypes方法向其显式注册类型及其对应的主键名称。

private void Divide(string filename)
{
List<OrderedItem> oldItems = new List<OrderedItem>();
List<OrderedItem> newItems = new List<OrderedItem>(); oldItems.Add(new OrderedItem() { ItemName = "Item A", Quantity = });
oldItems.Add(new OrderedItem() { ItemName = "Item B", Quantity = }); newItems.Add(new OrderedItem() { ItemName = "Item A", Quantity = });
newItems.Add(new OrderedItem() { ItemName = "Item C", Quantity = }); XPatchSerializer serializer = new XPatchSerializer(typeof(List<OrderedItem>));
//当OrderItem类型上未标记PrimaryKeyAttribute时,可以通过RegisterTypes方法向系统注册类型与主键的关系
Dictionary<Type, string[]> types = new Dictionary<Type, string[]>();
types.Add(typeof(OrderedItem), new string[] { "ItemName" });
serializer.RegisterTypes(types); FileStream fs = new FileStream(filename, FileMode.Create);
XmlWriter writer = new XmlTextWriter(fs, Encoding.UTF8);
serializer.Divide(writer, oldItems, newItems);
writer.Close();
}

控制是否序列化默认值

为减小序列化结果的大小,XPatchSerializer的构造函数提供了是否序列化类型默认值的参数设置。

public XPatchSerializer(System.Type pType, bool pSerializeDefalutValue)

当原始对象实例为Null时,才会进行是否序列化默认值的判断。默认设置为不序列化默认值。

以下示例展示了,由不同的参数设定产生的增量结果内容的区别。

首先对原有的CreditCard对象进行修改,增加int类型的参数CardCode。

public class CreditCard
{
public string CardExpiration { get; set; } public string CardNumber { get; set; } public int CardCode { get; set; }
}

使用相同的对象实例

CreditCard card1 = new CreditCard()
{
CardExpiration = "05/12",
CardNumber = ""
CardCode =
};

按照默认设置构造XPatchSerializer实例,并进行序列化

XPatchSerializer serializer = new XPatchSerializer(typeof(CreditCard));

context的内容将为:

<?xml version=""1.0"" encoding=""utf-8""?>
<CreditCard>
<CardExpiration>05/17</CardExpiration>
<CardNumber>9876543210</CardNumber>
</CreditCard>

指定需要序列化默认值构造XPatchSerializer实例,并进行序列化

XPatchSerializer serializer = new XPatchSerializer(typeof(CreditCard), true);

context的内容为(多出了<CardCode>0</CardCode>):

<?xml version=""1.0"" encoding=""utf-8""?>
<CreditCard>
<CardCode>0</CardCode>
<CardExpiration>05/17</CardExpiration>
<CardNumber>9876543210</CardNumber>
</CreditCard>

控制DateTime类型的输出格式及处理

XPatchSerializer的构造函数提供了字符串与 System.DateTime 之间转换时,如何处理时间值的参数设置。(XmlDateTimeSerializationMode)

public XPatchSerializer(System.Type pType, System.Xml.XmlDateTimeSerializationMode pMode)


XPatchLib 对象增量数据序列化及反序列化器 For .Net的更多相关文章

  1. 序列化对象C++对象的JSON序列化与反序列化探索

    新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,欢迎批评指正 一:背景 作为一名C++开发人员,我始终很期待能够像C#与JAVA那样,可以省力的进行对象的序列化与反序列化,但到现在为止,还没有找 ...

  2. C++对象的JSON序列化与反序列化探索完结-列表的序列化与反序列化

    在前两篇文章中,我们已经完成对普通对象以及复杂对象嵌套的序列化与反序列化,见如下地址: C++对象的JSON序列化与反序列化探索 C++对象的JSON序列化与反序列化探索续-复杂对象的序列化与反序列化 ...

  3. C++对象的JSON序列化与反序列化探索续-复杂对象的序列化与反序列化

    本文是基本上一篇博文进行改进而成,上一篇请见: C++对象的JSON序列化与反序列化探索 此处就不多说了,直接上代码. 1. 序列化基类 #pragma once #include <strin ...

  4. C++对象的JSON序列化与反序列化探索

    一:背景 作为一名C++开发人员,我一直很期待能够像C#与JAVA那样,可以轻松的进行对象的序列化与反序列化,但到目前为止,尚未找到相对完美的解决方案. 本文旨在抛砖引玉,期待有更好的解决方案:同时向 ...

  5. Java对象的serialVersion序列化和反序列化

    Java基础学习总结——Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种 ...

  6. XmlSerializer 对象的Xml序列化和反序列化

    http://www.cnblogs.com/yukaizhao/archive/2011/07/22/xml-serialization.html 这篇随笔对应的.Net命名空间是System.Xm ...

  7. XmlSerializer 对象的Xml序列化和反序列化,XMLROOT别名设置

    这篇随笔对应的.Net命名空间是System.Xml.Serialization:文中的示例代码需要引用这个命名空间.   为什么要做序列化和反序列化? .Net程序执行时,对象都驻留在内存中:内存中 ...

  8. C#中XML与对象之间的序列化、反序列化

    直接上代码: using System; using System.IO; using System.Text; using System.Xml; using System.Xml.Serializ ...

  9. php函数serialize()与unserialize() 数据序列化与反序列化

    php函数serialize()与unserialize()说明及案例.想要将已序列化的字符串变回 PHP 的值,可使用unserialize().serialize()可处理除了resource之外 ...

随机推荐

  1. JS 传播事件、取消事件默认行为、阻止事件传播

    1.事件处理程序的返回值 通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作.例如,表单提交按钮的onclick事件处理程序能通过返回false阻止浏览器提交表单,再如a标签的o ...

  2. activiti工作流的web流程设计器整合视频教程 SSM 和 独立部署

    本视频为activiti工作流的web流程设计器整合视频教程 整合Acitiviti在线流程设计器(Activiti-Modeler 5.21.0 官方流程设计器) 本视频共讲了两种整合方式 1. 流 ...

  3. linux(七)__shell脚本编程

    一.什么是shell脚本 shell除了是命令解释器之外还是一种编程语言,用shell编写的程序类似于DOS下的批处理程序. 它是用户与操作系统之间的一个接口. shell脚本语言非常擅长处理文本类型 ...

  4. java接口调用——webservice就是一个RPC而已

    很多新手一听到接口就蒙逼,不知道接口是什么!其实接口就是RPC,通过远程访问别的程序提供的方法,然后获得该方法执行的接口,而不需要在本地执行该方法.就是本地方法调用的升级版而已,我明天会上一篇如何通过 ...

  5. C#模拟HTTP Form请求上传文件

    using System; using System.Collections.Generic; using System.Text; using System.Net; using System.IO ...

  6. jsPanel插件Option总结

    jsPanel插件Option总结 学习jsPanel之余对相关的选项进行了总结,便于参考. # 选项名称 类别 简要说明 1 autoclose configuration 设置一个时间在毫秒后,面 ...

  7. SVN为什么比Git更好

    首先我表明一个根本的立场,我个人更喜欢用Git,但是,这仅仅是一个个人偏好.当我们需要将一种技术方案带给整个团队的时候,并不是由我们的个人偏好作为主要决定因素,而应该充分去权衡利弊,选择对团队,对公司 ...

  8. java代码走查审查规范

    分类 重要性 检查项 备注 命名         重要 命名规则是否与所采用的规范保持一致? 成员变量,方法参数等需要使用首字母小写,其余单词首字母大写的命名方式,禁止使用下划线(_)数字等方式命名不 ...

  9. Hive-0.x.x - Enviornment Setup

    All Hadoop sub-projects such as Hive, Pig, and HBase support Linux operating system. Therefore, you ...

  10. HTML中使图片居中显示

    注:imageId为图片id<style type="text/css"> #imageId{ display:block; position:relative; ma ...