使用 Microsoft.Data.SqlClient 流式处理方法来处理大值,无需先将完整值加载到应用内存中。 流式传输对于二进制文件、大文本、XML、JSON 以及服务器间传输非常有用。
流式处理不会使该值可查找,也不会使读取器上的并发操作变得安全。 连接、命令、读取器、源流和目的对象必须保持存活,直到传输完成。
本文中的示例假定一个名为 connection 的已打开 SqlConnection 指向 AdventureWorks 示例数据库。
C# 示例要求 using Microsoft.Data.SqlClient;。 使用 SqlDbType 或 CommandBehavior 的示例也需要 using System.Data;。
从 SQL 数据库中流式传输数据
使用 CommandBehavior.SequentialAccess 执行该命令,并按序号顺序读取列。 使用顺序访问时,当你越过一列后,不要再读同一行的前一列。
请使用以下 SqlDataReader 方法:
| 方法 | 结果 | 典型的SQL数据 |
|---|---|---|
| GetStream | Stream | 二进制、varbinary、图像和用户定义类型 (UDT) 数据 |
| GetTextReader | TextReader | 字符和 JSON 数据 |
| GetXmlReader | XmlReader | xml |
在请求可为 null 的列的流之前调用 IsDBNull。 一个流获取器与不兼容的 SQL 类型一起使用时,会抛出异常。 在读取下一列、推进行或进入下一个结果之前,先消耗或丢弃当前流。
GetStream 不受加密列支持。
GetTextReader 不支持对加密列的顺序访问。
CommandTimeout 会在 Microsoft.Data.SqlClient 执行和读取期间等待网络数据包时生效。 你的应用程序处理已接收数据所花费的时间不计入。 关于取消和超时,请参见 异步编程。
流式传输二进制数据
AdventureWorks 将文件内容存储在可为空的 varbinary(max)Production.Document.Document 列中。 本示例使用有界缓冲区将一个值复制到文件中:
using var command = new SqlCommand("""
SELECT TOP (1) Document
FROM Production.Document
WHERE Document IS NOT NULL
ORDER BY ModifiedDate DESC;
""", connection);
using SqlDataReader reader = command.ExecuteReader(
CommandBehavior.SequentialAccess);
if (reader.Read())
{
using Stream source = reader.GetStream(0);
using FileStream destination = File.Create("document.bin");
source.CopyTo(destination);
}
对于文本,使用 GetTextReader 并复制到 TextWriter。 对于 XML,使用 GetXmlReader 处理 XML 节点,但不将完整值转换为字符串。
将数据流式传输到 SQL 数据库
将 Value 设置为流式对象,并选择兼容的 SQL 类型:
| 值类型 | 兼容的参数类型 |
|---|---|
| Stream |
Binary, VarBinary, 以及遗留 Image 类型 |
| TextReader |
Char、NChar、Text、NText、旧版 Xml 和 NVarChar 类型、VarChar,以及 Json |
| XmlReader |
Char、NChar、Text、NText、旧版的 Xml 和 NVarChar 类型、VarChar 和 Json |
显式设置 Size 。
Size 限制传输量。 当你想发送完整源时,对 (max) 参数使用 -1。
对于有界内存传输,可以使用 VarBinary、VarChar 或 NVarChar 配合 Size = -1,或者使用 Xml 或 Json。 对于非 (max) 字符类型和二进制类型,以及旧版 Image、Text 和 NText 类型,Microsoft.Data.SqlClient 会在执行前将源缓冲到字节数组或字符串中。
流式传输参数值
该示例通过 varbinary(max) 参数发送文件,返回接收到的字节数。 它不存储该值:
using FileStream input = File.OpenRead("document.bin");
using var command = new SqlCommand(
"SELECT DATALENGTH(@content);",
connection);
command.Parameters.Add(
new SqlParameter("@content", SqlDbType.VarBinary, -1)
{
Value = input
});
long length = (long)command.ExecuteScalar()!;
Console.WriteLine($"{length} bytes");
在命令执行完成前保持输入对象开启。 将取消令牌传递给异步执行方法,或调用 Cancel 请求流操作的取消。 取消操作会尽力执行,但不保证成功。 操作可能会在取消生效前完成。
来自源流或读取器的异常会导致命令执行失败。 除非你能确定该对象的位置且数据库操作可以安全重复,否则不要从同一个对象重试。
将数据从一个 SQL 数据库流式传输到另一个 SQL 数据库
此示例将一个 AdventureWorks 文档流直接传递给另一个名为 destinationConnection 的已打开 SqlConnection 上的参数。 示例命令报告接收到的长度。 用目标数据库的参数化插入或更新命令替代:
using var readSource = new SqlCommand("""
SELECT TOP (1) Document
FROM Production.Document
WHERE Document IS NOT NULL
ORDER BY ModifiedDate DESC;
""", connection);
using SqlDataReader sourceReader = readSource.ExecuteReader(
CommandBehavior.SequentialAccess);
if (sourceReader.Read())
{
using Stream content = sourceReader.GetStream(0);
using var write = new SqlCommand(
"SELECT DATALENGTH(@content);",
destinationConnection);
write.Parameters.Add(
new SqlParameter("@content", SqlDbType.VarBinary, -1)
{
Value = content
});
Console.WriteLine(write.ExecuteScalar());
}
源命令和目标命令不共享事务。 如果你的应用需要跨服务器协调原子工作,则要单独设计和测试该事务边界。