数据协定中的 XML 和 ADO.NET 类型

Windows Communication Foundation (WCF) 数据协定模型支持直接表示 XML 的某些类型。 将这些类型序列化为 XML 时,序列化程序将写出这些类型的 XML 内容,而无需进一步处理。 支持的类型包括XmlElementXmlNode数组(但不包括XmlNode类型本身),以及实现IXmlSerializable的类型。 在数据库编程中,通常使用 DataSet 类型和 DataTable 类型,以及类型化数据集。 这些类型实现 IXmlSerializable 接口,因此在数据协定模型中可序列化。 本主题末尾列出了这些类型的一些特殊注意事项。

XML 类型

Xml 元素

XmlElement 类型使用其 XML 内容进行序列化。 例如,使用以下类型。

[DataContract(Namespace=@"http://schemas.contoso.com")]
public class MyDataContract
{
    [DataMember]
    public XmlElement myDataMember;
    public void TestClass()
    {
        XmlDocument xd = new XmlDocument();
        myDataMember = xd.CreateElement("myElement");
        myDataMember.InnerText = "myContents";
        myDataMember.SetAttribute
         ("myAttribute","myValue");
    }
}
<DataContract([Namespace]:="http://schemas.contoso.com")> _
Public Class MyDataContract
    <DataMember()> _
    Public myDataMember As XmlElement

    Public Sub TestClass()
        Dim xd As New XmlDocument()
        myDataMember = xd.CreateElement("myElement")
        myDataMember.InnerText = "myContents"
        myDataMember.SetAttribute("myAttribute", "myValue")

    End Sub
End Class

这序列化为 XML,如下所示:

<MyDataContract xmlns="http://schemas.contoso.com">  
    <myDataMember>  
        <myElement xmlns="" myAttribute="myValue">  
            myContents  
        </myElement>  
    </myDataMember>  
</MyDataContract>  

请注意,包装器数据成员元素 <myDataMember> 仍然存在。 无法删除数据协定模型中的此元素。 处理此模型( DataContractSerializerNetDataContractSerializer)的序列化程序可能会在此包装元素中发出特殊属性。 这些属性包括标准 XML 架构实例“nil”属性(允许 XmlElementnull)和“type”属性(允许 XmlElement 多态使用)。 此外,以下 XML 属性特定于 WCF:“Id”、“Ref”、“Type”和“Assembly”。 可以发出这些属性以支持与已启用的对象图保留模式或 XmlElement 一起使用 NetDataContractSerializer。 (有关对象图保留模式的详细信息,请参阅 序列化和反序列化

允许数组或集合 XmlElement ,并作为任何其他数组或集合进行处理。 也就是说,整个集合有一个包装元素,而数组中每个<myDataMember>都有一个单独的包装元素(就像前面的示例中的XmlElement一样)。

在反序列化时,反序列化器从传入的 XML 中创建 XmlElement。 反序列化程序还将提供一个有效的父 XmlDocument

请确保被反序列化为 XmlElement 的 XML 片段定义了它使用的所有前缀,并且不依赖于上级元素的任何前缀定义。 仅当使用 DataContractSerializer 从其他(非DataContractSerializer)源访问 XML 时,这才值得关注。

DataContractSerializer 一起使用时,XmlElement 可以进行多态分配,但只能分配给类型为 Object 的数据成员。 尽管它实现 IEnumerable,但 XmlElement 不能用作集合类型,也不能分配给 IEnumerable 数据成员。 与所有多元分配一样,DataContractSerializer 在生成的 XML 中发出数据协定名称。 在本例中,它是命名空间中的 http://schemas.datacontract.org/2004/07/System.Xml “XmlElement”。

使用NetDataContractSerializer,支持任何有效的XmlElement多态赋值(到ObjectIEnumerable)。

请勿尝试将任一序列化程序与派生自 XmlElement的类型一起使用,无论它们是否以多态方式分配。

XmlNode 数组

使用数组 XmlNode 非常类似于使用 XmlElement。 使用数组 XmlNode 比使用 XmlElement更灵活。 可以在数据成员包装元素内写入多个元素。 还可以在数据成员包装元素(如 XML 注释)中注入除元素以外的内容。 最后,可以将属性放入包装数据成员元素中。 这一切可以通过用特定的从XmlNode派生的类(例如XmlNodeXmlAttributeXmlElement)填充XmlComment的数组来实现。 例如,使用以下类型。

[DataContract(Namespace="http://schemas.contoso.com")]
public class MyDataContract
{
    [DataMember]
    public XmlNode[] myDataMember = new XmlNode[4];
    public void TestClass()
    {
        XmlDocument xd = new XmlDocument();
        XmlElement xe = xd.CreateElement("myElement");
        xe.InnerText = "myContents";
        xe.SetAttribute
         ("myAttribute","myValue");
    
        XmlAttribute atr = xe.Attributes[0];
        XmlComment cmnt = xd.CreateComment("myComment");
        
      myDataMember[0] = atr;
      myDataMember[1] = cmnt;
      myDataMember[2] = xe;
      myDataMember[3] = xe;
    }
}
<DataContract([Namespace]:="http://schemas.contoso.com")> _
Public Class MyDataContract
    <DataMember()> _
    Public myDataMember(3) As XmlNode

    Public Sub TestClass()
        Dim xd As New XmlDocument()
        Dim xe As XmlElement = xd.CreateElement("myElement")
        xe.InnerText = "myContents"
        xe.SetAttribute("myAttribute", "myValue")

        Dim atr As XmlAttribute = xe.Attributes(0)
        Dim cmnt As XmlComment = xd.CreateComment("myComment")

        myDataMember(0) = atr
        myDataMember(1) = cmnt
        myDataMember(2) = xe
        myDataMember(3) = xe

    End Sub

End Class

序列化后,生成的 XML 类似于以下代码。

<MyDataContract xmlns="http://schemas.contoso.com">  
  <myDataMember myAttribute="myValue">  
     <!--myComment-->  
     <myElement xmlns="" myAttribute="myValue">  
 myContents  
     </myElement>  
     <myElement xmlns="" myAttribute="myValue">  
       myContents  
     </myElement>  
  </myDataMember>  
</MyDataContract>  

请注意,数据成员包装器元素 <myDataMember> 包含属性、注释和两个元素。 这四个 XmlNode 实例是已经序列化的实例。

导致 XML 无效的 XmlNode 数组无法序列化。 例如,两个实例的 XmlNode 数组,其中第一个实例是一个 XmlElement ,第二个实例是 XmlAttribute 无效的,因为此序列不对应于任何有效的 XML 实例(没有位置附加属性)。

在对数组 XmlNode的反序列化时,将创建节点并使用传入 XML 中的信息填充。 反序列化程序还将提供一个有效的父 XmlDocument。 所有节点都进行反序列化,包括包装数据成员元素上的任何属性,但不包括 WCF 序列化程序放置在那里的属性(例如用于指示多态分配的属性)。 关于在 XML 片段中定义所有命名空间前缀的注意事项,适用于反序列化数组 XmlNode,这与反序列化 XmlElement 相同。

在启用对象图保存的情况下使用序列化程序时,对象相等性仅在数组级别 XmlNode (而不是单个 XmlNode 实例)上保留。

请勿尝试序列化数组XmlNode,如果其中一个或多个节点设置为null。 允许整个数组成员为 null,但数组中任意的单个元素不能为 XmlNode。 如果整个数组成员为 null,包装器数据成员元素包含一个特殊属性,指示它为 null。 在反序列化时,整个数组成员也变为 null。

只有常规数组 XmlNode 由序列化程序专门处理。 声明为其他集合类型的 XmlNode数据成员(或声明为派生自 XmlNode的类型数组的数据成员)不特别处理。 因此,除非它们还满足序列化的其他条件之一,否则它们通常不可序列化。

允许使用数组或 XmlNode 数组的集合。 整个集合都有一个包装元素,对于外部数组或集合中的每个数组<myDataMember>,有一个单独的包装元素(类似于XmlNode前面的示例中)。

使用 Array 实例填充 ObjectArray 类型的数据成员(或 IEnumerableXmlNode 类型的数据成员)时,将不会导致数据成员被视为 Array 实例的 XmlNode。 每个数组成员单独序列化。

DataContractSerializer 一起使用时,可以以多态方式分配 XmlNode 的数组,但只能分配给类型为 Object 的数据成员。 尽管它实现 IEnumerable,但不能将一 XmlNode 个数组用作集合类型,并分配给 IEnumerable 数据成员。 与所有多元分配一样,DataContractSerializer 在生成的 XML 中发出数据协定名称 - 在此情况下,它是 http://schemas.datacontract.org/2004/07/System.Xml 命名空间中的“ArrayOfXmlNode”。 与 NetDataContractSerializer一起使用时,支持任何有效的 XmlNode 数组分配。

架构注意事项

有关 XML 类型的架构映射的详细信息,请参阅 数据协定架构参考。 本部分总结了要点。

类型为XmlElement的数据成员被映射到使用以下匿名类型定义的元素。

<xsd:complexType>  
   <xsd:sequence>  
      <xsd:any minOccurs="0" processContents="lax" />  
   </xsd:sequence>  
</xsd:complexType>  

XmlNode 数组类型的数据成员映射到使用以下匿名类型定义的元素。

<xsd:complexType mixed="true">  
   <xsd:sequence>  
      <xsd:any minOccurs="0" maxOccurs="unbounded" processContents="lax" />  
   </xsd:sequence>  
   <xsd:anyAttribute/>  
</xsd:complexType>  

实现 IXmlSerializable 接口的类型

实现 IXmlSerializable 接口的类型得到 DataContractSerializer 的充分支持。 属性 XmlSchemaProviderAttribute 应始终应用于这些类型来控制其架构。

有三种实现的类型 IXmlSerializable:表示任意内容类型的类型、表示单个元素的类型和旧 DataSet 类型。

  • 内容类型使用XmlSchemaProviderAttribute属性所指定的架构提供程序方法。 该方法不返回 null,并且 IsAny 属性保留在其默认值 false 处。 这是这些 IXmlSerializable 类型最常见的用法。

  • 元素类型用于当IXmlSerializable类型必须控制其自己的根元素名称时。 若要将某个类型标记为元素类型,可以将 IsAny 属性 (Attribute) 上的 XmlSchemaProviderAttribute 属性 (Property) 设置为 true,或从架构提供程序方法返回 null。 对于元素类型,架构提供方法是可选的——可以在 XmlSchemaProviderAttribute 中指定 null 而不是方法名称。 但是,如果 IsAnytrue,并且指定了架构提供程序方法,则该方法必须返回 null。

  • DataSet 类型是 IXmlSerializable 的类型,这些类型没有被 XmlSchemaProviderAttribute 属性标记。 相反,它们依赖于 GetSchema 架构生成的方法。 此模式用于 DataSet 类型,其类型化数据集在 .NET Framework 的早期版本中派生了一个类,但现在它已过时,并且只有旧版本才支持它。 不要依赖此模式,并始终将 XmlSchemaProviderAttribute 应用于你的 IXmlSerializable 类型。

IXmlSerializable 内容类型

序列化属于之前定义的内容类型并实现 IXmlSerializable 的类型的数据成员时,序列化程序将为该数据成员写入包装元素,并将控制权传递给 WriteXml 方法。 实现 WriteXml 可以写入任何 XML,包括向包装元素添加属性。 WriteXml完成后,序列化程序将关闭该元素。

反序列化一个实现 IXmlSerializable 并且是先前定义的内容类型的数据成员时,反序列化程序会将 XML 读取器放置于数据成员的包装元素上,然后将控制权传递给 ReadXml 方法。 该方法必须读取整个元素,包括开始标记和结束标记。 确保代码 ReadXml 处理元素为空的情况。 此外,您的 ReadXml 实现也不应该依赖于以特殊方式进行命名的包装元素。 序列化程序选择的名称可能会有所不同。

允许以多元方式分配 IXmlSerializable 内容类型,例如,分配给 Object 类型的数据成员。 也允许类型实例为 null。 最后,可以使用启用了对象图保存功能的 IXmlSerializable 类型和 NetDataContractSerializer。 所有这些功能都需要 WCF 序列化程序将某些属性附加到 XML 架构实例命名空间中的包装元素(XML 架构实例命名空间中的“nil”和“type”,以及 WCF 特定命名空间中的“Id”、“Ref”、“Type”和“Assembly”)。

实现 ReadXml 时要忽略的属性

在将执行控制权交给您的 ReadXml 代码之前,反序列化程序会检查 XML 元素,检测这些特殊的 XML 属性,并对其进行处理。 例如,如果“nil”为true,则反序列化后生成一个 null 值,并且不会调用ReadXml。 如果检测到多态性,则元素的内容将反序列化,就好像它是另一种类型一样。 调用以多元方式分配的类型的 ReadXml 实现。 在任何情况下, ReadXml 实现都应忽略这些特殊属性,因为它们由反序列化程序处理。

IXmlSerializable 内容类型的架构注意事项

导出 IXmlSerializable 内容类型的架构时,将调用架构提供程序方法。 并将 XmlSchemaSet 传递给架构提供程序方法。 该方法可以将任何有效的架构添加到架构集。 架构集包含架构导出时已知道的架构。 当架构提供程序方法必须将项添加到架构集时,它必须确定 XmlSchema 该集中是否存在具有相应命名空间的项。 如果这样做,架构提供程序方法必须将新项添加到现有 XmlSchema项。 否则,它必须创建新 XmlSchema 实例。 这在使用IXmlSerializable类型的数组时非常重要。 例如,如果在命名空间“B”中有一个类型“IXmlSerializable”被导出为类型“A”,那么当调用架构提供程序方法时,架构集可能已经包含“B”的架构,以容纳“ArrayOfA”类型。

除了向 XmlSchemaSet添加内容类型之外,内容类型的架构提供程序方法还必须返回非 null 值。 它可能返回 XmlQualifiedName,指定用于给定 IXmlSerializable 类型的架构类型名称。 此限定名称还充当类型的数据协定名称和命名空间。 允许在架构提供程序方法返回时立即返回架构集中不存在的类型。 但是,预计当所有相关类型都已导出时(在Export上为所有相关类型调用XsdDataContractExporter方法并访问Schemas属性),该类型应该已经存在于模式集中。 在进行所有相关的 Schemas 调用之前访问 Export 属性可能会导致 XmlSchemaException。 有关导出过程的详细信息,请参阅 从类导出架构

架构提供程序方法也可以返回要使用的 XmlSchemaType。 该类型可以是匿名类型,也可能不是匿名类型。 如果它是匿名的,则每次将IXmlSerializable类型用作数据成员时,IXmlSerializable类型的架构都会被导出为匿名类型。 该 IXmlSerializable 类型仍具有数据协定名称和命名空间。 (如 数据协定名称 中所述确定,但 DataContractAttribute 属性不能用于自定义名称。如果不是匿名的,则它必须是其中 XmlSchemaSet一种类型。 这种情况相当于返回该类型的 XmlQualifiedName

此外,将为该类型导出全局元素声明。 如果类型没有应用 XmlRootAttribute 属性,则元素的名称和命名空间与数据契约相同,其“nillable”属性为 true。 唯一的例外是架构命名空间(http://www.w3.org/2001/XMLSchema);如果类型的数据协定在此命名空间中,则相应的全局元素位于空白命名空间中,因为它禁止向架构命名空间添加新元素。 如果类型应用了 XmlRootAttribute 属性,则使用以下属性导出全局元素声明: ElementNameNamespaceIsNullable。 应用 XmlRootAttribute 时的默认值为数据契约名称、空白命名空间,以及“nillable”为 true。

相同的全局元素声明规则适用于旧数据集类型。 请注意,XmlRootAttribute 无法重写通过自定义代码添加的全局元素声明,无论是使用构架提供程序方法添加到 XmlSchemaSet 中的,还是通过旧数据集类型的 GetSchema 添加的。

IXmlSerializable 元素类型

IXmlSerializable 元素类型要么将 IsAny 属性设置为 true,要么其架构提供程序方法返回 null

序列化和反序列化元素类型与序列化和反序列化内容类型非常相似。 但是,存在一些重要差异:

  • 实现 WriteXml 应只编写一个元素(当然可以包含多个子元素)。 它不应在此单个元素、多个同级元素或混合内容之外编写属性。 元素可能为空。

  • ReadXml 实现不应读取包装元素。 它应读取 WriteXml 生成的那一个元素。

  • 当定期序列化元素类型(例如,作为数据协定中的数据成员)时,序列化程序会在调用 WriteXml之前输出包装元素,就像内容类型一样。 然而,在顶层序列化元素类型时,序列化程序通常不会为 WriteXml 写入的元素周围输出包装元素,除非在使用 DataContractSerializerNetDataContractSerializer 构造函数构建序列化程序时显式指定了根名称和命名空间。 有关详细信息,请参阅 序列化和反序列化

  • 在顶层序列化元素类型时,如果在构造时没有指定根名称和命名空间,WriteStartObjectWriteEndObject 基本上没有任何作用,而 WriteObjectContent 会调用 WriteXml。 在此模式下,要序列化的对象不能为 null,并且不能以多态方式分配。 此外,对象图保留无法启用,且NetDataContractSerializer无法使用。

  • 在顶层反序列化元素类型时,如果在构造时没有指定根名称和命名空间,并且能够找到任何元素的开头,则 IsStartObject 会返回 trueReadObject 参数 verifyObjectName 设置为 true 的行为方式与 IsStartObject 实际读取对象之前的行为方式相同。 ReadObject 然后将控制权传递给 ReadXml 方法。

为元素类型导出的架构与前面部分所述的 XmlElement 类型相同,只是架构提供程序方法可以像对待内容类型一样,向 XmlSchemaSet 添加任何其他架构。 不允许将 XmlRootAttribute 属性与元素类型一起使用,并且永远不会针对这些类型发出全局元素声明。

与 XmlSerializer 的差异

IXmlSerializable接口和XmlSchemaProviderAttributeXmlRootAttribute属性也被XmlSerializer理解。 但是,在数据协定模型中对这些方法的处理方式存在一些差异。 下面总结了重要差异:

  • 架构提供程序方法必须是公共方法,才能在 XmlSerializer 中使用,但在数据合同模型中使用时则不必是公共方法。

  • 如果 IsAny 在数据协定模型中为 true,但在 XmlSerializer 中不为 true,则调用架构提供程序方法。

  • 当内容或旧数据集类型不存在该 XmlRootAttribute 属性时,导出 XmlSerializer 空白命名空间中的全局元素声明。 在数据协定模型中,使用的命名空间通常为数据协定命名空间,如前所述。

在创建与两种序列化技术一起使用的类型时,请注意这些差异。

导入 IXmlSerializable 架构

导入从 IXmlSerializable 类型生成的架构时,有一些可能性:

  • 生成的架构可以是有效的数据协定架构,如 数据协定架构参考中所述。 在这种情况下,可以像往常一样导入架构,并生成常规数据协定类型。

  • 生成的架构可能不是有效的数据协定架构。 例如,架构提供程序方法可能会生成涉及数据协定模型中不支持的 XML 属性的架构。 在这种情况下,可以将架构导入为 IXmlSerializable 类型。 默认情况下,此导入模式未启用,但可以轻松启用 -例如, 使用命令行切换到 ServiceModel 元数据实用工具工具(Svcutil.exe)。> 在 导入架构以生成类中详细介绍了这一点。 请注意,您必须直接处理您的类型实例的 XML。 还可以考虑使用不同的支持更广泛的架构的序列化技术 - 请参阅有关使用 XmlSerializer的主题。

  • 你可能想要重用代理中的现有 IXmlSerializable 类型,而不是生成新类型。 在这种情况下,导入架构到生成类型主题中所述的引用类型功能可用于指示要重用的类型。 这对应于在 svcutil.exe上使用 /reference 选项,该选项指定包含要重用类型的程序集。

在数据协定中表示任意 XML

XmlElementXmlNode 数组和 IXmlSerializable 类型,允许您将插入任意 XML 插入数据协定模型中。 并将DataContractSerializerNetDataContractSerializer此 XML 内容传递到正在使用的 XML 编写器,而不会干扰进程。 但是,XML 编写器可能会对所写入的 XML 强制实施某些限制。 具体而言,下面是一些重要示例:

  • XML 编写器通常不允许在编写另一个文档时使用 XML 文档声明(例如 <?xml version='1.0' ?>)。 您不能使用完整的 XML 文档并将其作为 Array 数据成员的 XmlNode 进行序列化。 要执行此操作,您必须提取出文档声明或使用自己的编码方案表示它。

  • WCF 提供的所有 XML 编写器都拒绝 XML 处理指令(<?...>?)和文档类型定义(<!...), >因为它们在 SOAP 消息中不允许。 同样,可以使用自己的编码机制来绕过此限制。 如果必须在生成的 XML 中包含这些内容,则可以编写使用支持它们的 XML 编写器的自定义编码器。

  • 实现 WriteXml时,请避免对 XML 编写器调用 WriteRaw 方法。 WCF 使用各种 XML 编码(包括二进制),很难或不可能使用 WriteRaw ,以便结果在任何编码中都可用。

  • 实现 WriteXml时,请避免使用 WCF 提供的 XML 编写器不支持的 WriteEntityRefWriteNmToken 方法。

使用数据集、类型化数据集和数据表

数据协定模型中完全支持使用这些类型。 使用这些类型时,请考虑以下几点:

  • 这些类型的架构(尤其是 DataSet 及其类型化派生类)可能无法与某些不支持 WCF 的平台互操作,或在使用这些平台时导致使用性差。 此外,使用类型 DataSet 可能会对性能造成影响。 最后,它可能会使将来更难对应用程序进行版本控制。 请考虑在您的合同中使用显式定义的数据合同类型,而不是DataSet的类型。

  • 导入 DataSetDataTable 架构时,引用这些类型非常重要。 使用 Svcutil.exe 命令行工具,可以通过将 System.Data.dll 程序集名称传递给 /reference 开关来实现此目的。 如果导入类型化数据集架构,则必须引用类型化数据集的类型。 使用 Svcutil.exe,将类型化数据集的程序集的位置传递给 /reference 开关。 有关引用类型的详细信息,请参阅 导入架构以生成类

对数据协定模型中类型化数据集的支持有限。 类型化数据集可以序列化和反序列化,并且可以导出其架构。 但是,数据协定架构导入无法从架构中生成新的类型化数据集类型,因为它只能重复使用现有数据类型。 通过对 Svcutil.exe 使用 /r 开关,可以指向现有类型化数据集。 如果尝试在不使用 /r 开关的情况下对使用类型化数据集的服务使用 Svcutil.exe,则会自动选择替代序列化程序 (XmlSerializer)。 如果必须使用 DataContractSerializer 且必须从架构生成数据集,则可使用以下过程:生成类型化数据集类型(通过将 Xsd.exe 工具与 /d 开关结合起来用于服务)、编译类型,然后在 Svcutil.exe 上使用 /r 开关来指向这些类型。

另请参阅