wcf基础教程之 数据契约的序列化DataContractSerializer

上一篇博客我们说明了数据契约的前身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,我们可以得出以下结论:

  1. 父类的属性在序列化成xml的时候在子节点的前面。
  2. 序列化中xml元素的排序时按照字母顺序排序。
  3. 子类序列化会吧父类一起序列化,并且序列化到子类中

对于第一点,可能这是我发现的一个规律,对于第二点,可能有朋友要说了,这只是看了一个就得出这个结论,有点草率,如果添加了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属性的除外。

posted @ 2013-03-28 23:19  baidixing  阅读(1732)  评论(1编辑  收藏  举报