下面的问题是托管存储过程如何将结果返回给它的调用者而不是通过 SqlDataReader 来使用它。这可以通过称为 SqlPipe 的新类来实现。通过 SqlContext 类的静态方法可以使此类的实例对托管存储过程可用。SqlPipe 有几种方法可以将结果返回给存储过程的调用者。这两个类都是在 Sqlaccess.dll 中定义的。
SqlPipe
在 SqlPipe 类中可以使用的方法中,最容易理解的就是 Execute 方法,它将命令对象作为参数接受。这个方法主要执行命令,并且没有使执行的结果可用于托管框架,而是将结果发送给存储过程的调用者。发送结果的这种形式在语义上与将语句嵌入 T-SQL 存储过程内是一样的。在本文前面描述的性能方面,SqlPipe.Execute 与 T-SQL 是等价的。
create proc proc1 as
select col1 from dbo.table1;
The equivalent in C# would be:
public static void proc1()
{
System.Data.SqlServer.SqlCommand cmd=SqlContext.GetCommand();
cmd.CommandText= "select col1 from dbo.table1";
SqlContext.GetPipe().Execute(cmd);
}
对于返回的数据是由执行的查询直接产生的情况,SqlPipe.Execute 可以很好地工作。然而,在某些情况下可能希望1)从数据库中获得结果,进行操作或者转换,然后发送它们,或者 2)将结果发送回原地而不是本地 SQL Server 实例。
SqlPipe 提供了一组可以协同工作以使应用程序可以将任何结果返回给调用者的方法:SendResultsStart、SendResultsRow 和 SendResultsEnd。在很大程度上,这些 API 类似于对扩展存储过程的开发人员可用的 srv_describe 和 srv_sendrow API。
SendResultsStart 将 SqlDataRecord 作为参数接受,并且指示返回的新结果集的开头。该 API 从记录对象读取元数据信息,并且将其发送给调用者。该方法有重载,以允许发送元数据以及记录中的实际值。
随后可以返回行,方法是对要发送的每行调用一次 SendResultsRowows。在发送完全部所需的行之后,需要调用 SendResultsEnd 来指示结果集的结尾。
例如,下面的 C# 代码片段表示一个存储过程,它读取 XML 文档(来自 MSDN 的 Really Simple Syndication [RSS] 供给),使用 System.Xml 类进行解析,并且以相关的形式返回信息。请注意,这些代码应该创建为 EXTERNAL_ACCESS(或 UNSAFE)程序集,因为访问 Internet 所需的代码访问安全 (CAS) 权限只有在这些权限集中才是可用的。
// Retrieve the RSS feed
XPathDocument doc = new XPathDocument("http://msdn.microsoft.com/sql/rss.xml");
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator i = nav.Select("//item");
// create metadata for four columns
// three of them are string types and one of the is a datetime
SqlMetaData[] rss_results = new SqlMetaData[4];
rss_results[0] = new SqlMetaData("Title", SqlDbType.NVarChar, 250);
rss_results[1] = new SqlMetaData("Publication Date", SqlDbType.DateTime);
rss_results[2] = new SqlMetaData("Description", SqlDbType.NVarChar, 2000);
rss_results[3] = new SqlMetaData("Link", SqlDbType.NVarChar, 1000);
// construct the record which holds metadata and data buffers
SqlDataRecord record = new SqlDataRecord(rss_results);
// cache a SqlPipe instance to avoid repeated calls to SqlContext.GetPipe()
SqlPipe sqlpipe = SqlContext.GetPipe();
// send the metadata, do not send the values in the data record
sqlpipe.SendResultsStart(record, false);
// for each xml node returned, extract four pieces
// of information and send back each item as a row
while (i.MoveNext())
{
record.SetString(0, (string)
i.Current.Evaluate("string(title[1]/text())"));
record.SetDateTime(1, DateTime.Parse((string)
i.Current.Evaluate("string(pubDate[1]/text())")));
record.SetString(2, (string)
i.Current.Evaluate("string(description[1]/text())"));
record.SetString(3, (string)
i.Current.Evaluate("string(link[1]/text())"));
sqlpipe.SendResultsRow(record);
}
// signal end of results
sqlpipe.SendResultsEnd();
注:在 SendResultsStart 和 SendResultsEnd 调用之间,SqlPipe 被设置为繁忙状态,调用除 SendResultsRow 之外的任何 Send 方法都会导致错误发生。SqlPipe 处于繁忙状态中时,SendingResults 属性被设置为 TRUE。
表值函数
CLR 集成也启用了对用托管语言编写的表值函数 (TVF) 的支持。与 T-SQL 相似,TVF 主要用于返回表结果。最显著的不同在于,T-SQL 表值函数临时将结果存储在工作表中,而 CLR TVF 则能够对产生的结果数据进行流式处理。这意味着结果在从函数返回之前不需要物化。
注 T-SQL 还具有内联 TVF 的概念,即不临时存储结果。内联 TVF 在大部分语义上便于指定子查询(可能带有参数)。
托管 TVF 返回 ISqlReader 接口,这是由 SqlClient 和 SqlServer (in-proc) 托管提供程序中的 SqlDataReader 实现的一种只进光标抽象。查询处理器调用此接口上的 Read() 方法,以在每行返回 FALSE 之前获取它。
