发布时间:2014-08-01 00:00 来源:未知
一、简介
Microsoft SQL Server 2005 中的重大更改之一是包含了 XML 数据类型。此数据类型是第一类类型,就像 INT 或 VARCHAR 一样,而且 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.Data 和 System.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 用来在客户端上检索此特殊的流。在 SQL Server 2005 中,SELECT ... FOR XML 语句通过许多方式得到了增强。这里仅提少数几种:
1. |
在大多数情况下,当您需要 SQL Server 2000 中的 FOR XML EXPLICIT 模式时,有一种新的、便于使用的 FOR XML PATH 模式。 |
2. |
使用 TYPE 指令,除了生成流之外,您还可以生成 XML 数据类型列。 |
3. |
可以嵌套 FOR XML 表达式。 |
4. |
SELECT ... FOR XML 可以使用 ROOT 指令生成 XML 文档以及 XML 片段。 |
5. |
您可以将标准的 XSD 架构预先挂起到流。 |
通过引用 ADO.NET 2.0 中的关系 datatype 枚举,您可以初步了解到 XML 是一种一流的关系数据库类型。System.Data.DbType 和 System.Data.SqlDbType 分别包含 DbType.Xml 和 SqlDbType.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 提供 VARCHAR 或 NVARCHAR 到 XML 数据类型的自动转换。请注意,此转换还可以发生在客户端,如以下示例中所示。向存储过程 insert_xml 提供 string/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')
SELECT ... FOR XML 也会生成 XML 片段。语句 SELECT job_id, min_lvl, max_lvl FROM jobs 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() 方法总是创建一个 XmlReader,它通过使用新的 XmlReaderSettings 类来支持片段,如下所示:
// 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 ... FOR 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() 方法,就像使用 SqlConnection、SqlCommand 和 SqlDataReader 一样。在通过 SqlDataReader 读取 XML 列的情况下,可以为每一行分配一个 XmlReader。请记住,为了支持在同一行的列中向后移动,或者移动到下一行,XmlReader 的内容要在内存中缓冲。当您使用 SqlCommand 的 CommandBehavior.SequentialAccess 时,整个 XmlReader 不会在内存中缓冲,但您使用此存取方法时必须小心。使用 CommandBehavior.SequentialAccess 时,与列相关联的 XmlReader 在行集中移动到下一列之前必须完全消耗掉;在移动到下一列之后,XmlReader 看似有效,但调用它的 Read() 方法不会产生任何数据。当您使用 ExecuteXmlReader 或 ExecuteScalar 代替 ExecuteReader 时,您无需同样多的了解此行为,但此时也别忘了关闭/处理 XmlReader。
四、在客户端上使用 XML 架构支持SQL Server 2005 支持强类型化的 XML,这意味着 XML 必须符合某个 XML 架构或 XML 架构集。通过使用 SQL Server 中的架构集合可以实现这种支持。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 COLLECTIONs 检索 XML 架构,并将它们隐藏在客户端上以完成客户端的验证。这可以防止用户或 Web 服务将架构无效的 XML 发送到用于 SQL Server 存储的客户端,从而节省了一些反复的操作,但是,必须考虑两处警告/澄清。首先,依赖于从 SQL Server 所获取的 XML 架构信息如同依赖于任何高速缓存的客户端元数据。可能某人已使用 T-SQL ALTER XML SCHEMA 语句改变了架构集合,或者甚至删除了架构集合并重新创建它,从而使得您的客户端的检查无用。您可以在 CREATE/ALTER/DROP XML SCHEMA DDL 语句上使用新的 EVENT NOTIFICATION,以防止出现异常。然后,您将使用自定义代码监视 Service Broker SERVICE,这类似于使用带有查询通知的 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 COLLECTION。这需要 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 COLLECTION 中的 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...FOR XML 语句上使用新的 SQL Server 2005 XMLSCHEMA 选项时,也产生 XML 架构:
SELECT * FROM authors FOR XML AUTO, ELEMENTS, XMLSCHEMA
您还可以结束片段,并在客户端上为这些类型的查询检索 XMLSCHEMASet,尽管实际上在当前的测试版执行这样的操作还有一些限制。
五、小结我已经描述了如何使用新的 XML 类型和新的 FOR XML 功能在 Microsoft ADO.NET SqlClient 中使用来自 Microsoft SQL Server 的 XML,以及如何使用 SqlClient 将 XML 插入到 SQL Server 表中。实际上 DataSet 中还有些增强的 XML 功能我没有提到,以后将有一系列关于 ADO 2.0 DataSet 增强功能的文章。您可以在 MSDN 在线文章 XML Support in Microsoft SQL Server 2005 中读到关于使用 SQL Server XML 数据的更多信息,作者是 Shankar Pal、Mark Fussell 和 Irwin Dolobowsky;在 MSDN 在线文章 What's New in FOR XML in Microsoft SQL Server 2005 中读到关于对 SELECT...FOR XML 的增强的更多信息,作者是 Michael Rys;以及在 Mark Fussell 的 What's New in System.Xml for Visual Studio 2005 and the .NET Framework 2.0 Release 中读到关于 .NET 2.0 中的 System.Xml 的更多信息。关于 XML 与 ADO.NET 2.0 和 SqlClient 在很多级别上进行了集成的这种说法是安全的;数据模型的集成比以往更为严密。
关于作者
Bob Beauchemin 是 DevelopMentor 的一名教员、课程作者和数据库课程联络人。他在以数据为中心的分布式系统方面有超过二十五年的设计师、程序员和管理员经验。他写过关于 ADO.NET、OLE DB 和 SQL Serverfor Microsoft Systems Journal、SQL ServerMagazine 以及其他内容的文章,并且是 A First Look at SQL Server 2005for Developers 和 Essential ADO.NET 的作者。
一、简介
Microsoft SQL Server 2005 中的重大更改之一是包含了 XML 数据类型。此数据类型是第一类类型,就像 INT 或 VARCHAR 一样,而且 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.Data 和 System.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 用来在客户端上检索此特殊的流。在 SQL Server 2005 中,SELECT ... FOR XML 语句通过许多方式得到了增强。这里仅提少数几种:
1. |
在大多数情况下,当您需要 SQL Server 2000 中的 FOR XML EXPLICIT 模式时,有一种新的、便于使用的 FOR XML PATH 模式。 |
2. |
使用 TYPE 指令,除了生成流之外,您还可以生成 XML 数据类型列。 |
3. |
可以嵌套 FOR XML 表达式。 |
4. |
SELECT ... FOR XML 可以使用 ROOT 指令生成 XML 文档以及 XML 片段。 |
5. |
您可以将标准的 XSD 架构预先挂起到流。 |
通过引用 ADO.NET 2.0 中的关系 datatype 枚举,您可以初步了解到 XML 是一种一流的关系数据库类型。System.Data.DbType 和 System.Data.SqlDbType 分别包含 DbType.Xml 和 SqlDbType.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 提供 VARCHAR 或 NVARCHAR 到 XML 数据类型的自动转换。请注意,此转换还可以发生在客户端,如以下示例中所示。向存储过程 insert_xml 提供 string/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')
SELECT ... FOR XML 也会生成 XML 片段。语句 SELECT job_id, min_lvl, max_lvl FROM jobs 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() 方法总是创建一个 XmlReader,它通过使用新的 XmlReaderSettings 类来支持片段,如下所示:
// 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 ... FOR 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() 方法,就像使用 SqlConnection、SqlCommand 和 SqlDataReader 一样。在通过 SqlDataReader 读取 XML 列的情况下,可以为每一行分配一个 XmlReader。请记住,为了支持在同一行的列中向后移动,或者移动到下一行,XmlReader 的内容要在内存中缓冲。当您使用 SqlCommand 的 CommandBehavior.SequentialAccess 时,整个 XmlReader 不会在内存中缓冲,但您使用此存取方法时必须小心。使用 CommandBehavior.SequentialAccess 时,与列相关联的 XmlReader 在行集中移动到下一列之前必须完全消耗掉;在移动到下一列之后,XmlReader 看似有效,但调用它的 Read() 方法不会产生任何数据。当您使用 ExecuteXmlReader 或 ExecuteScalar 代替 ExecuteReader 时,您无需同样多的了解此行为,但此时也别忘了关闭/处理 XmlReader。
四、在客户端上使用 XML 架构支持SQL Server 2005 支持强类型化的 XML,这意味着 XML 必须符合某个 XML 架构或 XML 架构集。通过使用 SQL Server 中的架构集合可以实现这种支持。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 COLLECTIONs 检索 XML 架构,并将它们隐藏在客户端上以完成客户端的验证。这可以防止用户或 Web 服务将架构无效的 XML 发送到用于 SQL Server 存储的客户端,从而节省了一些反复的操作,但是,必须考虑两处警告/澄清。首先,依赖于从 SQL Server 所获取的 XML 架构信息如同依赖于任何高速缓存的客户端元数据。可能某人已使用 T-SQL ALTER XML SCHEMA 语句改变了架构集合,或者甚至删除了架构集合并重新创建它,从而使得您的客户端的检查无用。您可以在 CREATE/ALTER/DROP XML SCHEMA DDL 语句上使用新的 EVENT NOTIFICATION,以防止出现异常。然后,您将使用自定义代码监视 Service Broker SERVICE,这类似于使用带有查询通知的 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 COLLECTION。这需要 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 COLLECTION 中的 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...FOR XML 语句上使用新的 SQL Server 2005 XMLSCHEMA 选项时,也产生 XML 架构:
SELECT * FROM authors FOR XML AUTO, ELEMENTS, XMLSCHEMA
您还可以结束片段,并在客户端上为这些类型的查询检索 XMLSCHEMASet,尽管实际上在当前的测试版执行这样的操作还有一些限制。
五、小结我已经描述了如何使用新的 XML 类型和新的 FOR XML 功能在 Microsoft ADO.NET SqlClient 中使用来自 Microsoft SQL Server 的 XML,以及如何使用 SqlClient 将 XML 插入到 SQL Server 表中。实际上 DataSet 中还有些增强的 XML 功能我没有提到,以后将有一系列关于 ADO 2.0 DataSet 增强功能的文章。您可以在 MSDN 在线文章 XML Support in Microsoft SQL Server 2005 中读到关于使用 SQL Server XML 数据的更多信息,作者是 Shankar Pal、Mark Fussell 和 Irwin Dolobowsky;在 MSDN 在线文章 What's New in FOR XML in Microsoft SQL Server 2005 中读到关于对 SELECT...FOR XML 的增强的更多信息,作者是 Michael Rys;以及在 Mark Fussell 的 What's New in System.Xml for Visual Studio 2005 and the .NET Framework 2.0 Release 中读到关于 .NET 2.0 中的 System.Xml 的更多信息。关于 XML 与 ADO.NET 2.0 和 SqlClient 在很多级别上进行了集成的这种说法是安全的;数据模型的集成比以往更为严密。
关于作者
Bob Beauchemin 是 DevelopMentor 的一名教员、课程作者和数据库课程联络人。他在以数据为中心的分布式系统方面有超过二十五年的设计师、程序员和管理员经验。他写过关于 ADO.NET、OLE DB 和 SQL Serverfor Microsoft Systems Journal、SQL ServerMagazine 以及其他内容的文章,并且是 A First Look at SQL Server 2005for Developers 和 Essential ADO.NET 的作者。