ADO.NET 2.0 中的 XML 数据类型支持:从 SQL Server 2005 处理 XML

 

Bob Beauchemin
DevelopMentor

2004 年 10 月

适用于:
   Microsoft SQL Server 2005
   Microsoft ADO.NET 2.0
   XML

总结:了解 Microsoft ADO.NET 2.0 和 Microsoft SQL Server 2005 中 XML 支持的改进如何协同工作,以便更轻松地处理应用程序中的 XML 数据。 (13 个打印页)

目录

简介
是 XML 还是字符串?
文档、片段和 FOR XML 支持
在客户端上使用 XML 架构支持
结论

简介

Microsoft SQL Server 2005 中的一个量子更改是包含 XML 数据类型。 此数据类型是第一类类型,与 INTVARCHAR 一样,SQL Server 2005 允许使用一系列特定于 XML 的函数就地查询和处理此数据类型。 还支持在数据库中存储 XML 架构的集合,从而启用基于数据库的架构验证。 此外,SQL Server 2005 大大扩展了 XML 组合 (SELECT...FOR XML 方言) ,扩展 OpenXML () XML 分解函数,并在 XML 数据类型上提供新的 nodes () 函数,以便进行更轻量的分解。

有了数据库服务器上所有这些新增和增强的 XML 功能,Microsoft ADO.NET 2.0 中的 SqlClient 数据提供程序也得到了增强也就不足为奇了。 此外,ADO.NET DataSet 也进行了更改以支持 XML 类型的 DataColumn并且 System.DataSystem.Xml 之间的“集成点”已扩大。 在本文中,我将探讨在客户端上使用 SQL Server 2005 XML 数据类型。

SQL Server 2005 可以生成两种类型的 XML 输出。 语句 SELECT * FROM AUTHORS FOR XML AUTO 生成 XML 流,而不是单列单行行集。 这种类型的输出与 SQL Server 2000 年没有变化。 XML 流输出仅由于查询分析器工具的限制,才以单列单行行集的形式出现在查询分析器中SQL Server。 可以通过特殊的唯一标识符名称“XML_F52E2B61-18A1-11d1-B105-000805F49916B”将此流与“普通”列区分开来。 此名称实际上是基础 TDS ((表格数据流、SQL Server网络格式) 分析程序)的指示器,该分析器应将列流式传输到客户端,而不是作为普通行集发送。 有一种特殊的方法 SqlCommand.ExecuteXmlReader 在客户端上检索此特殊流。 在 2005 SQL Server,SELECT...FOR XML 方言在很多方面都得到了增强。 若要仅提及其中几个:

  1. 在 SQL Server 2000 中需要 FOR XML EXPLICIT 模式的大多数情况下,可以使用一种新的易于使用的 FOR XML PATH 模式。
  2. 除了使用 TYPE 指令的流之外,还可以生成 XML 数据类型列。
  3. 可以嵌套 FOR XML 表达式。
  4. 选择。。。FOR XML 可以使用 ROOT 指令生成 XML 文档和 XML 片段。
  5. 可以在流前面附加标准 XSD 架构。

通过引用 ADO.NET 2.0 中的关系数据类型枚举,第一个指示 XML 现在是一类关系数据库类型。 System.Data.DbTypeSystem.Data.SqlDbType 分别包含 DbType.XmlSqlDbType.Xml的其他值。 System.Data.SqlTypes 命名空间中还有一个新类 SqlXml。 此类充当 XML 类型值之上的 XmlReader 实例的工厂。 我将通过一些简单的代码进行演示。 假设我有一个如下所示的SQL Server表:

CREATE TABLE xmltab (
  id INT IDENTITY PRIMARY KEY,
  xmlcol XML)

我可以使用以下 ADO.NET 2.0 代码在客户端上访问此表。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;

void GetXMLColumn {
// "Generic Coding..." article for shows how to
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd = new SqlCommand(
       "select * from xmltab", conn))
 {
  conn.Open();
  SqlDataReader rdr = cmd.ExecuteReader();
  DataTable t = rdr.GetSchemaTable();

  while (rdr.Read())
    {
      SqlXml sx = rdr.GetSqlXml(1);
      XmlReader xr = sx.CreateReader();
      xr.Read(); 
      Console.WriteLine(xr.ReadOuterXml());
    }
 }
}

浏览 GetSchemaTable 生成的 DataTable 时返回的列元数据正确标识列:

ProviderType: 25 (25 = XML)
ProviderSpecificDataType: System.Data.SqlTypes.SqlXml
DataType: System.Xml.XmlReader
DataTypeName:

看起来像内置SQL Server的任何其他类型。 请注意,此列的“.NET 类型”是 XmlReader,对于 .NET,它看起来就像从文件加载或使用 XmlDocument 类生成的任何 XML 一样。 在 ADO.NET 2.0 中的存储过程或参数化语句中使用 XML 数据类型列作为参数同样简单:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;

void AddARow {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd = new SqlCommand(
       "insert xmltab(xmlcol) VALUES(@x)", conn))
 {
  conn.Open();
  cmd.Parameters.Add("@x", SqlDbType.Xml);

  // connect the parameter value to a file
  XmlReader xr = XmlReader.Create("somexml.xml");
  cmd.Parameters[0].Value = new SqlXml(xr);
  int i = cmd.ExecuteNonQuery();
 }
}

是 XML 还是字符串?

上述代码中的两种方法在 SqlTypes 中使用特定于 SQL Server 的数据类型。 当我使用 SqlReader 的更泛型访问器方法 GetValue () 时,该值就大不相同了。 列不显示为 XmlReader, 而是显示为 .NET String 类。 请注意,即使元数据将列的 .NET 数据类型标识为 XmlReader,也不能仅将列强制转换为 XmlReader。 使用 除 GetSqlXml () 之后的任何访问器将返回一个字符串。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;

void GetXMLColumn {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd = new SqlCommand(
       "select * from xmltab", conn))
 {
  conn.Open();
  SqlDataReader rdr = cmd.ExecuteReader();
  // prints "System.String"
  Console.WriteLine(rdr[1].GetType()); 
  
  // fails, invalid cast
  XmlReader xr = (XmlReader)rdr[1];

  // this works
  string s = (string)rdr[1];
 }
}

即使使用 SqlReader.GetProviderSpecificValue () 方法也会返回字符串。 这有点异常,因为 GetProviderSpecificFieldType 正确返回 System.Sql.Types.SqlXml。 这看起来可能是提供程序的当前 beta 版本中的问题;暂时我不会使用此方法。

// System.Data.SqlTypes.SqlXml
Console.WriteLine(rdr.GetProviderSpecificFieldType(1));

// System.Data.SqlTypes.SqlString
Object o = rdr.GetProviderSpecificValue(1);
Console.WriteLine(o.GetType());

SqlClient 为 XML 参数提供对称功能;还可以将 String 数据类型与这些一起使用。 能否在 XML 类型 (NVARCHAR) 传入字符串取决于SQL Server自动将 VARCHARNVARCHAR 转换为 XML 数据类型这一事实。 请注意,此转换也可以在客户端发生,如以下示例所示。 为存储过程 提供insert_xml字符串/NVARCHAR 自动转换为 XML 的任一方法都行得当。

-- T-SQL stored procedure definition
CREATE PROCEDURE insert_xml(@x XML)
AS
INSERT xmltab(xmlcol) VALUE(@x)

// client-side code
using System;
using System.Data;
using System.Data.SqlClient;

void InsertXMLFromClient {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd1 = new SqlCommand(
       "INSERT xmltab(xmlcol) VALUES(@x)", conn))
using (SqlCommand cmd2 = new SqlCommand(
       " insert_xml", conn))
 {
  string str = "<somedoc/>";

  conn.Open();
  
  // server-side conversion
  cmd1.Parameters.Add("@x", SqlDbType.NVarChar);
  cmd1.Parameters[0].Value = str;
  cmd1.ExecuteNonQuery();

  // client-side conversion works too
  cmd2.CommandType = CommandType.StoredProcedure;
  cmd2.Parameters.Add("@x", SqlDbType.Xml);
  cmd2.Parameters[0].Value = s;
  cmd2.ExecuteNonQuery();
 }
}

文档、片段和 FOR XML 支持

SQL Server 2005 中的 XML 数据类型支持 XML 文档和 XML 文档片段。 片段不同于文档,片段可以包含多个顶级元素和裸文本节点。 使用类型化的 XML 列/变量/参数,可以指定是否允许使用 DOCUMENT (片段) 或 CONTENT (片段) 规范。 CONTENT 是默认的,非类型化的 XML 允许片段。 此 T-SQL 代码演示了对片段的支持:

CREATE TABLE xmltab (
  id INT IDENTITY PRIMARY KEY,
  xmlcol XML)
GO

-- insert a document
INSERT xmltab VALUES('<doc/>')
-- fragment, multiple top-level elements
INSERT xmltab VALUES('<doc/><doc/>')
-- fragment, bare text node
INSERT xmltab VALUES('Hello World')
-- even this fragment works
INSERT xmltab VALUES('<doc/>sometext')

XML 片段也由 SELECT...FOR XML。 语句 SELECT job_id,min_lvl,max_lvl FROM 作业 FOR XML AUTO 将生成以下输出。 请注意,有多个根元素。

<jobs job_id="1" min_lvl="10" max_lvl="10" />
<jobs job_id="2" min_lvl="200" max_lvl="250" />
<jobs job_id="3" min_lvl="175" max_lvl="225" />
<jobs job_id="4" min_lvl="175" max_lvl="250" />
<!-- some jobs rows deleted for compactness -->

使用 SqlXml 支持文档和片段。 SqlXmlCreateReader () 方法始终使用新的 XmlReaderSettings 类创建支持片段的 XmlReader,如下所示:

// pseudocode from SqlXml.CreateReader
    Stream stm = stm; // stream filled from column (code elided)
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.ConformanceLevel = ConformanceLevel.Fragment;
    XmlReader xr = XmlReader.Create(
       stm, String.Empty, null, null,settings);

如果以相同的方式构造 XmlReader ,则可以在输入参数中使用 XML 片段。 尽管使用 SqlXml 类型时内置了片段支持,但需要小心处理包含片段的 XmlReader 。 请注意,调用 XmlReader.GetOuterXml () 将仅提供第一个片段;若要定位 XmlReader 以获取后续片段,则必须再次调用 XmlReaderRead 方法。 本文稍后将对此进行演示。

T-SQL 的“SELECT...FOR XML“生成了 XML 流,而不是单列单行行集。 它还以二进制格式(而不是 XML 的标准序列化)提供 XML。 由于格式差异,也因为“SELECT...对于 XML“始终生成的片段,需要使用特殊方法来使用它。 为此,SqlClient 实现了特定于提供程序的方法 SqlCommand.ExecuteXmlReader。 使用 SQL Server 2000 和 ADO 1.0/1.1 时,需要使用 ExecuteXmlReader 来获取 FOR XML 查询的结果,除非你想要使用一些需要字符串串联的相当丑陋的解决方法。 借助 SQL Server 2005 FOR XML 增强功能并支持 XML 作为数据类型,只需使用 ExecuteXmlReader 从SQL Server获取单个 XML 流。 由于所有为 SQL Server 2000 编写的代码都使用此方法,因此在 ADO.NET 2.0 中支持和增强此方法。

可以使用 ExecuteXmlReader 从“FOR XML”查询检索任何流,如在以前的版本中一样。 此外,此方法还支持检索由普通 SELECT 语句生成的 XML 数据类型列。 唯一的注意事项是,当普通 SELECT 语句返回多行时, ExecuteXmlReader 仅返回第一行的内容。 以下示例使用与前面的示例中相同的表来说明这一点:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;

void UseExecXmlReader {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd1 = new SqlCommand(
       "select * from pubs..authors for xml auto,root('root')", conn))
using (SqlCommand cmd2 = new SqlCommand(
       "select * from pubs..authors for xml auto", conn))
using (SqlCommand cmd3 = new SqlCommand(
       "select * from xmltab", conn))
 {
  conn.Open();
  // contains document
  XmlReader xr1 = cmd1.ExecuteXmlReader();
  // contains fragment
  XmlReader xr2 = cmd2.ExecuteXmlReader();
  // contains contents of first row in xmltab only
  XmlReader xr3 = cmd3.ExecuteXmlReader();
  // use XmlReaders, then
  xr1.Dispose(); xr2.Dispose(); xr3.Dispose();
 }
}

若要完成有关在 ADO.NET 2.0 中获取 XML 的讨论,在各种使用方案中提及 XmlReader 内容的生存期很有帮助。 调查 XmlReader 的生存期还有助于了解 SqlClient 完成的缓冲,以及如何使用此数据实现最佳性能。 XmlReader 使用资源,若要释放这些资源,应调用 Close () Dispose () 方法,就像使用 SqlConnectionSqlCommandSqlDataReader 一样。 在通过 SqlDataReader 读取 XML 列的情况下,可以为每行分配 一个 XmlReader 。 请记住,为了支持在同一行中的列向后移动或移动到下一行, XmlReader 的内容将在内存中缓冲。 使用 SqlCommandCommandBehavior.SequentialAccess 时,整个 XmlReader 不会在内存中缓冲,但在使用此访问方法时必须更加小心。 在使用 CommandBehavior.SequentialAccess 时,必须先完全使用与列关联的 XmlReader,然后才能移动到行集中的下一列;移动到下一列后,XmlReader 似乎有效,但调用其 Read () 方法不会产生任何数据。 使用 ExecuteXmlReaderExecuteScalar 而不是 ExecuteReader 时,无需了解此行为,但不要忘记在此处关闭/释放 XmlReader

在客户端上使用 XML 架构支持

SQL Server 2005 支持强类型 XML,这意味着 XML 必须符合 XML 架构或 XML 架构集。 通过在 SQL Server 中使用 XML 架构集合来启用此支持。 XML 架构集合的定义与任何其他SQL Server对象一样,XML 架构存储在 SQL Server 中。 T-SQL DDL CREATE 语句和 XML 架构集合用法如下所示:

CREATE XML SCHEMA COLLECTION books_xsd
AS
-- one or more XML schemas here
GO

CREATE TABLE typed_xml (
  id INT IDENTITY PRIMARY KEY,
  -- require books_col content to be schema-valid
  books_col XML(books_xsd)
)
-- validated here
INSERT typed_xml VALUES('<!-- some document -->')
-- validated here too
UPDATE typed_xml
  SET books_col.modify('<!-- some XQuery DML -->')
  WHERE id = 1

从客户端使用 SQL Server 2005 中的强类型 XML 数据时,验证是在服务器上完成的,而不是在客户端上完成。 例如,如果使用前面示例中所示的 AddARow 方法向typed_xml表添加行,则会在验证发生之前通过网络将数据发送到SQL Server。 不过,通过一点点工作,可以从SQL SERVER XML SCHEMA COLLECTION 检索 XML 架构并将其存放在客户端上以实现客户端验证。 这可以通过阻止用户或 Web 服务将架构无效的 XML 发送到客户端进行SQL Server存储来节省一些往返,但必须考虑两点注意事项/说明。 首先,依赖于从 SQL Server 提取的 XML 架构信息就像依赖于任何缓存的客户端元数据一样。 可能有人使用 T-SQL ALTER XML SCHEMA 语句更改了架构集合,甚至删除了架构集合并重新创建,使客户端检查无用。 可以通过对 CREATE/ALTER/DROP XML SCHEMA DDL 语句使用新的 EVENT NOTIFICATION 来防范意外。 然后,你将使用自定义代码监视 Service Broker 服务,类似于在查询通知中使用 SqlNotificationRequest 时。 EVENT NOTIFICATIONs 是 SQL Server 2005 中的新增功能,但与上一篇文章 ADO.NET 2.0 中的查询通知类似。 其次,请记住,即使你在客户端上执行 XML 架构验证,SQL Server也会在服务器上重新检查。 无法指示SQL Server“我知道SQL Server架构类型 XML 的实例是架构有效的,不要费心自己检查它。

若要执行客户端 XML 架构验证,可以使用 T-SQL 函数 xml_schema_namespace () 检索SQL Server XML SCHEMA 集合。 这需要 XML 架构集合名称和数据库架构名称,因为 XML 架构集合是数据库架构范围。 我们可以将它们硬编码到程序中,也可以从客户端行集元数据中提取它们。 使用上面的 SqlDataReaderGetSchemaTable 方法或使用新的 SqlMetaData 类,可以轻松获取与特定列关联的架构集合的名称。 下面是一个简短的代码示例:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.Sql;

void GetCollectionInfo {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd = new SqlCommand(
       "select books_col from typed_xml", conn))
{
  conn.Open();
  // fetch only SQL Server metadata
  SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly);

  SqlMetaData md = rdr.GetSqlMetaData(0);
  string database = md.XmlSchemaCollectionDatabase;
  string schema = md.XmlSchemaCollectionOwningSchema;
  string collection = md.XmlSchemaCollectionName;
}
}

确定要检索的 XML 架构集合后,可以使用 T-SQL 函数将其检索到客户端 XmlSchemaSet 中。 请注意,此示例还演示如何使用 XmlReader 检索片段。 这是必需的,因为 XML SCHEMA 集合中可能有多个 XML 架构; xml_schema_namespace 函数将集合中的所有 XML 架构作为片段返回。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;
using System.Xml.Schema;

void GetSchemaSet {
// get a connection string from a config file
string s = GetConnectStringFromConfigFile("xmldb");
using (SqlConnection conn = new SqlConnection(s))
using (SqlCommand cmd = new SqlCommand(
       "SELECT xml_schema_namespace(N'dbo',N'books_xsd')", conn))
 {
  XmlSchemaSet ss = new XmlSchemaSet();
  conn.Open();
  SqlDataReader rdr = cmd.ExecuteReader();
  rdr.Read();
  XmlReader xr = rdr.GetSqlXml(0).CreateReader();

  do
  {
    ss.Add(XmlSchema.Read(xr, null));
    xr.Read();
  }
  while (xr.NodeType == XmlNodeType.Element);
 }
}

有了 XmlSchemaSet ,我们可以将客户端 XML 验证代码集成到 Add 例程中,并请! 客户端验证。

void ValidateAndStore(XmlSchemaSet ss)
{
  // associate the XmlSchemaSet with the XmlReader
  XmlReaderSettings settings = new XmlReaderSettings();
  settings.Schemas = ss;
  string s = GetConnectStringFromConfigFile("xmldb");
  using (XmlReader xr = XmlReader.Create(
    "file://c:/temp/somefile.xml", settings))
  // get a connection string from a config file
  using (SqlConnection conn = new SqlConnection(s))
  using (SqlCommand cmd = new SqlCommand(
    "insert typed_xml values(@x)", conn))
  {
    try
    {
      conn.Open();
      // should throw an exception here if not schema valid
      cmd.Parameters.AddWithValue("@x", new SqlXml(xr));
      int i = cmd.ExecuteNonQuery();
    }
    catch (Exception e)
    {
       Console.WriteLine(e.Message);
    }
  }
}

尽管客户端 XML 架构验证不是每个应用程序都需要的,但很高兴知道,如果应用程序需要,它可用。 在 SELECT... 上使用新的 SQL Server 2005 XMLSCHEMA 选项时,也会生成 XML 架构。对于 XML,如下所示:

   SELECT * FROM authors FOR XML AUTO, ELEMENTS, XMLSCHEMA

也可以拆分片段并在客户端为这些类型的查询检索 XmlSchemaSet ,但实际上在当前 beta 版本中这样做存在一些限制。

结论

我介绍了如何使用新的 XML 类型和新的 FOR XML 功能在 Microsoft ADO.NET SqlClient 中使用来自 Microsoft SQL Server的 XML,以及如何使用 SqlClient 将 XML 插入SQL Server表中。 实际上, DataSet 中有我未提及的改进的 XML 功能;即将推出有关 ADO 2.0 DataSet 增强功能的文章系列。 你可以阅读有关使用 SQL Server XML 数据的详细信息,请参阅 MSDN 在线文章 XML Support in Microsoft SQL Server 2005 由 Shankar Pal、Mark Fussell 和 Irwin Dolobowsky;有关 SELECT 的增强功能... MICROSOFT SQL Server 2005 版 MICROSOFT SQL SERVER 2005 FOR XML 的新增功能,以及 Mark Fussell System.Xml for Visual Studio 2005 和 .NET Framework 2.0 版本中有关 .NET 2.0 中System.Xml的详细信息。 可以肯定地说,XML 与 ADO.NET 2.0 和 SqlClient 在许多级别集成;数据模型的集成是有史以来最紧密的。

 

关于作者

Bob Beauchemin 是 DevelopMentor 的讲师、课程作者和数据库课程联络人。 他拥有超过 25 年的架构师、程序员和以数据为中心的分布式系统的管理员经验。 他为 Microsoft Systems Journal、SQL Server 杂志等撰写了有关 ADO.NET、OLE DB 和SQL Server的文章,并且是该杂志的作者 面向开发人员和基本 ADO.NET 的 2005SQL Server初探