上一篇博客我们说明了数据契约的前身Xml的序列化,这次还是言归正传,回到wcf的技术上来,分析一下DataContractSerializer 。
首先我们必须了解wcf默认是通过xml进行数据传输,但是并不意味着就一定要用这种,还有轻量级的json。DataContractSerializer继承自XmlObjectSerializer,是可以直接对.Net对象进行序列化操作,但是DatacontractSerializer的使用更加方便。
要使一个类成为数据契约,我们必须要通过DataContractAttribute进行标注,然后通过应用DataMemberAttribute特性来标注它的属性或字段,才可以让这个类成为一个数据契约,在网络间进行传输。
DataContract的属性很少,但是都特别有用,在序列化的时候有一个属性IsReference,表示在进行序列化的时候是否要保持对象现有的引用结构,默认值为false。这个属性在我们进行序列化的时候会举例说明其中的区别。
DataMember的属性也很少,IsRequired 表示是否是必须的,默认值为false。这个属性是什么意思呢?就是说在序列化的时候这个属性如果不提供,则会报错,或者在反序列化的时候,如果这个属性没有,那么也会报错,总之就是必须这个属性存在。
DataMember还有一个属性EmitDefaultValue,表示数据成员的值等于默认值的情况下,是否还需要将其序列化到xml中,默认值为true,表示总是将其序列化。
我提出一个问题?就是如何计算wcf的默认可以处理的对象个数?因为如果我们创建很多的对象进行序列化,那么可能会出现一个超出处理对象最大值的异常信息?这个也是我们必须要考虑的。
在数据契约中,还存在一种特殊的,我们叫做已知类型 KnownType。因为wcf在序列化和反序列化的时候,必须明确的知道对象的类型。这个放到下一篇我们来讨论什么时候会用到已知类型。
定义一个数据契约是那么的简单,以至于我们总是认为wcf还是那么的容易。请记住这么一句话,师傅领进门,修行在个人。入门简单这是师傅的功劳,但是修行怎么样,就要看你自己的造化了。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 7 namespace Chinaer.WcfDemo.Contracts 8 { 9 [DataContract] 10 public class Person 11 { 12 [DataMember] 13 public string ID { get; set; } 14 15 public string UserName { get; set; } 16 17 [DataMember] 18 /// <summary> 19 /// 私有属性 20 /// </summary> 21 private string UserPwd { get; set; } 22 23 //公有字段 24 [DataMember] 25 public string ParentName; 26 //私有字段 27 [DataMember] 28 private string BrotherName; 29 30 } 31 }
我们仍然在控制台进行输出到xml文件查看他们的区别。
1 static void Main(string[] args) 2 { 3 Chinaer.WcfDemo.Contracts.PersonName person=new Contracts.PersonName(){ 4 ID="1", ParentName="parent", UserName="guohz" 5 }; 6 DataContractSerialize<Contracts.PersonName>(person, "person.xml"); 7 Console.Read(); 8 } 9 /// <summary> 10 /// DataContractSerializer序列化 11 /// </summary> 12 /// <typeparam name="T"></typeparam> 13 /// <param name="instance"></param> 14 /// <param name="fileName"></param> 15 public static void DataContractSerialize<T>(T instance, string fileName) 16 { 17 DataContractSerializer serializer = new DataContractSerializer(typeof(Chinaer.WcfDemo.Contracts.PersonName)); 18 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 19 { 20 serializer.WriteObject(writer, instance); 21 22 } 23 Process.Start(fileName); 24 }
数据契约序列化除了序列化器不同之外,其他的代码步骤都相同。生成的xml文件。
1 <PersonName xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 2 xmlns="http://schemas.datacontract.org/2004/07/Chinaer.WcfDemo.Contracts"> 3 <BrotherName i:nil="true" /> 4 <ID>1</ID> 5 <ParentName>parent</ParentName> 6 <UserPwd i:nil="true" /> 7 </PersonName>
大家注意:上面生成的xml我们可以看出,无论是公有属性,还有私有属性或私有字段,只要加上了DataMember attribute,那么就会被序列化。
现在我们来想一下,如果有主从关系的两个类,最后序列化会生成什么呢?动手试试吧。
我们再次定义一个类继承自刚才定义的类,然后我们序列化子类,我们一起来查看结果。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 7 namespace Chinaer.WcfDemo.Contracts 8 { 9 [DataContract] 10 public class PersonName 11 { 12 [DataMember] 13 public string ID { get; set; } 14 15 public string UserName { get; set; } 16 17 [DataMember] 18 /// <summary> 19 /// 私有属性 20 /// </summary> 21 private string UserPwd { get; set; } 22 23 //公有字段 24 [DataMember] 25 public string ParentName; 26 //私有字段 27 [DataMember] 28 private string BrotherName; 29 30 } 31 32 [DataContract] 33 public class ParentName:PersonName 34 { 35 [DataMember] 36 public string Parent { get; set; } 37 } 38 39 }
序列化子类生成的xml为:
1 <ParentName xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 2 xmlns="http://schemas.datacontract.org/2004/07/Chinaer.WcfDemo.Contracts"> 3 <BrotherName i:nil="true" /> 4 <ID>1</ID> 5 <ParentName>parent</ParentName> 6 <UserPwd i:nil="true" /> 7 <Parent>parent</Parent> 8 </ParentName>
对比父类生成的xml,我们可以得出以下结论:
- 父类的属性在序列化成xml的时候在子节点的前面。
- 序列化中xml元素的排序时按照字母顺序排序。
- 子类序列化会吧父类一起序列化,并且序列化到子类中
对于第一点,可能这是我发现的一个规律,对于第二点,可能有朋友要说了,这只是看了一个就得出这个结论,有点草率,如果添加了order属性,那么肯定顺序会改变。对于第三个,应该说是常识,但是我在上面说过一个关于引用的问题,这个问题就和这个有关。
下面我们添加order属性,来再次查看生成的xml的结果。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 7 namespace Chinaer.WcfDemo.Contracts 8 { 9 [DataContract] 10 public class PersonName 11 { 12 [DataMember(Order = 2)] 13 public string ID { get; set; } 14 15 public string UserName { get; set; } 16 17 [DataMember(Order = 1)] 18 /// <summary> 19 /// 私有属性 20 /// </summary> 21 private string UserPwd { get; set; } 22 23 //公有字段 24 [DataMember(Order = -1)] 25 public string ParentName; 26 //私有字段 27 [DataMember(Order = 0)] 28 private string BrotherName; 29 30 } 31 32 [DataContract] 33 public class ParentName : PersonName 34 { 35 [DataMember(Order = 1)] 36 public string Parent { get; set; } 37 } 38 39 }
请注意上面的代码,我们为order赋值的时候,有赋值-1和0,子类我们赋值为1.那么根据order的作用,父类应该是按照order排序,子类的位置我们根据生成的xml文件来查看。
如果你是直接运行的话,那么你会得到如下的异常信息提示。
我们上面说过,特别注意我们为order赋值-1.其中我们的ParentName属性的order就是-1.如果我没记错的话,我们最好设置0以上的数字
1 <ParentName xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 2 xmlns="http://schemas.datacontract.org/2004/07/Chinaer.WcfDemo.Contracts"> 3 <BrotherName i:nil="true" /> 4 <UserPwd i:nil="true" /> 5 <ID>1</ID> 6 <ParentName>parent</ParentName> 7 <Parent>parent</Parent> 8 </ParentName>
加了order属性之后,xml元素的顺序果然改变了,但是请注意最后一个,仍然是子类的元素。所以请注意这个规律就可以。
下面我们来一起了解一下,如果类之间有引用关系,那么如果保持引用和不保持引用状态生成的xml有哪些不同呢?
我们取消personName和parentName的父子关系,然后重修修改序列化,添加是否需要保持引用状态的bool值。
1 static void Main(string[] args) 2 { 3 Chinaer.WcfDemo.Contracts.ParentName person = new Contracts.ParentName() 4 { 5 Parent = "parent" 6 }; 7 Chinaer.WcfDemo.Contracts.PersonName personName = new Contracts.PersonName() 8 { 9 ID = "id", 10 ParentName = person, 11 ParentName2 = person, 12 UserName = "guozhiqi" 13 }; 14 DataContractSerialize<Contracts.PersonName>(personName, "person.xml",true);//保持引用状态 15 DataContractSerialize<Contracts.PersonName>(personName, "person2.xml", false);//不保持引用状态 16 Console.Read(); 17 } 18 /// <summary> 19 /// DataContractSerializer序列化 20 /// </summary> 21 /// <typeparam name="T"></typeparam> 22 /// <param name="instance"></param> 23 /// <param name="fileName"></param> 24 public static void DataContractSerialize<T>(T instance, string fileName, bool preserveReference) 25 { 26 DataContractSerializer serializer = new DataContractSerializer(typeof(Chinaer.WcfDemo.Contracts.ParentName), null, int.MaxValue, false, preserveReference, null); 27 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 28 { 29 serializer.WriteObject(writer, instance); 30 31 } 32 Process.Start(fileName); 33 }
保持对象引用状态生成的xml文件为:
1 <PersonName xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 2 z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" 3 xmlns="http://schemas.datacontract.org/2004/07/Chinaer.WcfDemo.Contracts"> 4 <ParentName2 z:Id="2"> 5 <Parent z:Id="3">parent</Parent> 6 </ParentName2> 7 <BrotherName i:nil="true" /> 8 <UserPwd i:nil="true" /> 9 <ID z:Id="4">id</ID> 10 <ParentName z:Ref="2" i:nil="true" /> 11 </PersonName>
不保持对象引用状态生成的xml文件为:
1 <PersonName xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 2 xmlns="http://schemas.datacontract.org/2004/07/Chinaer.WcfDemo.Contracts"> 3 <ParentName2><Parent>parent</Parent></ParentName2> 4 <BrotherName i:nil="true" /> 5 <UserPwd i:nil="true" /> 6 <ID>id</ID> 7 <ParentName> 8 <Parent>parent</Parent></ParentName> 9 </PersonName>
我对xml理解的不深,无法准确的说出其中的区别,但是我看到了在不保持对象引用状态的情况下,对象引用了几次就会出现几次。我们可以得出这么一个结论,在大数据量的情况下,保持对象的引用状态可以减少传输的数据量。
刚才那个问题我们还没有回答?就是对象的个数如何计算?为了回答这个问题,我们来看一个例子
请注意我们的序列化代码:
1 static void Main(string[] args) 2 { 3 Chinaer.WcfDemo.Contracts.ParentName person = new Contracts.ParentName() 4 { 5 Parent = "parent" 6 }; 7 Chinaer.WcfDemo.Contracts.PersonName personName = new Contracts.PersonName() 8 { 9 ID = "id", 10 ParentName = person, 11 ParentName2 = person, 12 UserName = "guozhiqi" 13 }; 14 15 List<Contracts.ParentName> listParent = new List<Contracts.ParentName>(); 16 for (int i = 0; i <= 10000; i++) 17 { 18 Contracts.ParentName parentName = new Contracts.ParentName() 19 { 20 Parent = i.ToString() 21 }; 22 listParent.Add(parentName); 23 } 24 25 DataContractSerialize<List<Contracts.ParentName>>(listParent, "list.xml", false); 26 27 //DataContractSerialize<Contracts.PersonName>(personName, "person.xml", true);//保持引用状态 28 //DataContractSerialize<Contracts.PersonName>(personName, "person2.xml", false);//不保持引用状态 29 Console.Read(); 30 } 31 /// <summary> 32 /// DataContractSerializer序列化 33 /// </summary> 34 /// <typeparam name="T"></typeparam> 35 /// <param name="instance"></param> 36 /// <param name="fileName"></param> 37 public static void DataContractSerialize<T>(T instance, string fileName, bool preserveReference) 38 { 39 DataContractSerializer serializer = new DataContractSerializer(typeof(T), null, 50, false, preserveReference, null); 40 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 41 { 42 serializer.WriteObject(writer, instance); 43 44 } 45 Process.Start(fileName); 46 }
请看代码中的50,表示的就是wcf每次最多处理的对象数,默认值为65536,我们设置为50就是为了出现那么久违的异常信息。这次我们序列化的事一个列表。
在异常信息中有一个属性MaxItemsInObjectGraph,表示的就是处理的最大对象数,如果出现了这个异常信息的话,我们就需要调高这个值。
如何处理才能解决这个异常信息呢?其实有多种方法:
首先第一个就是ServiceBehavior 有一个属性值MaxItemsInObjectGraph,就可以设置大小。
还有就是通过配置文件的方式。
1 <behaviors> 2 <serviceBehaviors> 3 <behavior name="metaDataBehavior"> 4 <serviceMetadata httpGetEnabled="true"/> 5 <dataContractSerializer maxItemsInObjectGraph="65536"/> 6 </behavior> 7 </serviceBehaviors> 8 </behaviors>
在行为Behaviors节点下,有一个dataContractSerializer 可以通过它来设置对象的大小。
知道了如何来调整大小,那么我们应该如何计算对象的个数呢?
1 [DataContract] 2 public class Order 3 { 4 [DataMember] 5 public string UserName { get; set; } 6 [DataMember] 7 public string UserPwd { get; set; } 8 } 9 public class Exec 10 { 11 public void Execute() 12 { 13 List<Order> list = new List<Order>(); 14 for (int i = 0; i < 10; i++) 15 { 16 Order order = new Order() { UserName="userName" }; 17 list.Add(order); 18 } 19 20 } 21 22 }
比如说上面的代码,如果我们序列化list对象,那么相当于我们需要处理多少对象呢?
list对象本身算一个,每个order对象算一个,每个order对象的每个属性算一个,就是1+10+20=31 。序列化一个简单的列表就需要处理31个对象。
如果我们处理一个复杂的数据,那么就会更多了,所以一定要计算好,尽量不要超越设定的最大值。
数据契约序列化DataContractserializer 是xmlObjectSerializer的延伸,是简单化的处理。其中也有区别,例如xml序列化是按照元素的出现位置排序,而datacontractserializer是按照字母顺序排序。当然存在order属性的除外。
我又回来了,回到了技术最前线,