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

例如,有系统中需要配置日志的记录路径和日志文件的命名方式。默认的日志是放在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. 《连载 | 物联网框架ServerSuperIO教程》2.服务实例的配置参数说明

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍  <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制 一.综述 SuperIO(SIO)定位 ...

  2. 2016年6月份那些最实用的 jQuery 插件专辑

    jQuery 是一个快速.流行的 JavaScript 库,jQuery 用于文档处理.事件处理.动画和 Ajax 交互非常简单,学习曲线也很平坦.2016年6月的 jQuery 插件专辑里,我们选择 ...

  3. div仿textarea使高度自适应

    今天真的有些无语,在百度上找了很多关于textarea和input高度自适应的代码,并且考虑到了要判断textarea的滚动条,从而动态改变它的高度,直到我搜索了这个让我目瞪狗呆的办法…… <d ...

  4. Android 命令行执行工具类

    最近在做android项目的时候,需要执行命令行命令,之前在网上找的不仅杂乱而且错误多,于是自己写了一份. 话不多说,直接上代码 import android.util.Log; import jav ...

  5. iOS网络2——NSURLSession使用详解

    原文在此 一.整体介绍 NSURLSession在2013年随着iOS7的发布一起面世,苹果对它的定位是作为NSURLConnection的替代者,然后逐步将NSURLConnection退出历史舞台 ...

  6. Hibernate 系列 02 - Hibernate介绍及其环境搭建

    引导目录: Hibernate 系列教程 目录 昨晚喝多了,下午刚清醒,继续搞Hibernate.走起. 觉得还行的话,记得点赞哈,给我这个渣渣点学习的动力.有错误的话也请指出,省的我在错误上走了不归 ...

  7. 当没有用 EXISTS 引入子查询时,在选择列表中只能指定一个表达式。

    当没有用 EXISTS 引入子查询时,在选择列表中只能指定一个表达式.比如 select * from T_Employee where FNumber not in ( select top 5*  ...

  8. 高级数据过滤(like)

    单字符过滤 '_' select * from T_Employee where FName like  '_erry' 多字符过滤 '%' select * from T_Employee wher ...

  9. SQL Server 2012 新特性:服务角色管理

    数据库角色管理,已经可以使用alter role,create role和drop role. 2012增加了几个ddl语句,可以操作服务级别的角色管理,   CREATE SERVER ROLE 用 ...

  10. javaweb项目jsp跳转servlet Error instantiating servlet class 问题

    问题: HTTP Status 500 - Error instantiating servlet class RecommenderServlet type Exception report mes ...