代码改变世界

WCF 第六章 序列化与编码 保留引用和循环引用

2010-12-18 22:26  DanielWise  阅读(1403)  评论(2编辑  收藏  举报

关于引用和序列化有两个重要的问题。这两个问题都是关于通过引用保留跟踪的。当你试着优化需要序列化的数据总量或者当在客户端和服务端共享类型信息时保留引用将会起很重要的作用。

  引用保留允许同样的数据在一个数据契约中引用多于一次而不用重复数据。当你使用数据可能被引用多于一次的列表,数组和哈希表等数据结构时,保留引用这是一个很常用的场景。通过保留引用,数据在它第一次出现在数据契约中时被序列化然这个数据的所有顺序出现都以引用的形式。这可以在序列化时并且数据被引用多次的时候实现减少数据总量的预期重要效果。

  循环引用是指一个对象维持对子对象的引用,子对象还会对其引用。关于循环引用的一个例子是一个子对象维持到父对象的父子关系。这些情况的类型在面向对象编程中很常用。对象维护循环引用的问题是序列化不可能没有对引用保留的支持。任何序列化结构将会在一个试着序列化对象的无终止循环中终止。引用保留允许对使用的数据添加一个引用而不是对数据反复序列化。

  DataContractSerializer 默认不允许保留引用。引用保留对NetDataContractSerializer和XmlSerializer默认开启。如果你打算在客户端和服务端之间共享类型信息你可以使用这些序列化方法中的一个。否则,你还可以在DataContractSerializer上使用一个自定义属性来支持引用保留。

提示 使用IXmlSerializable 来支持引用保留

支持引用保留需要使用代码来实现如果你计划使用IXmlSerializable来使用自定义序列化

  让我们看一下列表6.14中的一个例子。首先,一个列表从List<Employee>中创建。然后几个Employee对象被添加到列表中。

列表6.14保留引用的需要

[ServiceContract]
    public interface IEmployeeInformation
    {
        [OperationContract]
        Employee[] GetEmployeesOfTheMonth();
    }
    public class EmployeeInformation : IEmployeeInformation
    {
        public EmployeeInformation()
        {
        }

        public Employee[] GetEmployeesOfTheMonth()
        {
            List<Employee> list = new List<Employee>(6);
            Employee employee1 = new Employee(1, "John", "Doe");
            Employee employee2 = new Employee(2, "Jane", "Doe");
            Employee employee3 = new Employee(3, "John", "Smith");

            list.Add(employee1);
            list.Add(employee2);
            list.Add(employee3);
            list.Add(employee1);
            list.Add(employee2);
            list.Add(employee3);

            return list.ToArray();
        }
    }

  默认情况下,DataContractSerializer将会将数据的每一个引用都作为一个单独的拷贝序列化。列表6.15的输出显示了Employee1, Employee2,Employee3出现了多次。

列表6.15 没有保留引用的序列化列表

 <ArrayOfEmployee xmlns="http://schemas.datacontract.org/2004/07/SerializerWorker" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
- <Employee>
  <EmployeeID>1</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee>
  <EmployeeID>2</EmployeeID> 
  <FirstName>Jane</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee>
  <EmployeeID>3</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Smith</LastName> 
  </Employee>
- <Employee>
  <EmployeeID>1</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee>
  <EmployeeID>2</EmployeeID> 
  <FirstName>Jane</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee>
  <EmployeeID>3</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Smith</LastName> 
  </Employee>
  </ArrayOfEmployee>

  为了保留引用,通过向DataContractSerializer构造函数的preserveObjectReference参数传递一个true值来应用一个自定义行为并创建一个实例。行为是WCF中的扩展结构,允许你修改运行时的默认行为;它将会在第五章"行为"中介绍。在这种情况下它允许我们通过修改DataContractSerializer默认行为来支持引用保留。列表6.16完成一个自定义行为来描述这个。

列表6.16 使用一个自定义行为来完成引用保留

using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;

namespace SerializerUtility
{
    public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior
    {
        #region IOperationBehavior 成员

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(operationDescription);
            innerBehavior.ApplyClientBehavior(operationDescription, clientOperation);
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(operationDescription);
            innerBehavior.ApplyDispatchBehavior(operationDescription, dispatchOperation);
        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        #endregion
    }

    public class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
    {
        private static XmlObjectSerializer CreateDataContractSerializer(Type type, string name,
            string ns, IList<Type> knownTyps)
        {
            return CreateDataContractSerializer(type, name, ns, knownTyps);
        }

        public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription)
        {
        }

        public override System.Runtime.Serialization.XmlObjectSerializer CreateSerializer(Type type, 
            string name, string ns, IList<Type> knownTypes)
        {
            return CreateDataContractSerializer(type, name, ns, knownTypes);
        }

        public override System.Runtime.Serialization.XmlObjectSerializer CreateSerializer(Type type, 
            System.Xml.XmlDictionaryString name, System.Xml.XmlDictionaryString ns, IList<Type> knownTypes)
        {
            return new DataContractSerializer(type, name, ns, knownTypes, 0x7fff, false, true, null);
        }
    }
}

 对我们的操作契约应用这个属性的结果在列表6.17中显示。输出结果显示Employee1, Employee2和Employee3仅出现一次,但是它们现在使用一个属性标记,z:Id, 当做一个引用标记符使用。其他的对这些对象的引用标识符使用z:Ref属性表示。

列表6.17 使用引用保留的序列化列表

- <ArrayOfEmployee xmlns="http://schemas.datacontract.org/2004/07/SerializerWorker" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
- <Employee z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <EmployeeID>1</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <EmployeeID>2</EmployeeID> 
  <FirstName>Jane</FirstName> 
  <LastName>Doe</LastName> 
  </Employee>
- <Employee z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
  <EmployeeID>3</EmployeeID> 
  <FirstName>John</FirstName> 
  <LastName>Smith</LastName> 
  </Employee>
  <Employee z:Ref="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" /> 
  <Employee z:Ref="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" /> 
  <Employee z:Ref="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" /> 
  </ArrayOfEmployee>