SqlDataReader - (n)varchar 与 v (n)varchar(max) 或 xml / json 上的 GetStream 与 GetBytes

SqlDataReader - GetStream vs GetBytes on (n)varchar vs v (n)varchar(max) or for xml / for json

我一直在研究一种方法,以流形式提供对大型 SQL 结果的访问,尤其是当查询使用 for json 或 for xml 子句时。

我注意到当查询 returns json 或 xml 时 SqlDataReader 不允许使用 GetStream() 方法,这是预期的根据文档:

'Invalid attempt to GetStream on column 'c'. The GetStream function can only be used on columns of type Binary, Image, Udt or VarBinary.'

但是, 允许使用 GetBytes()

当返回数据为 varchar(max)nvarchar(max) 时,行为相同。 GetStream() 不行,但是 GetBytes() 可以。

对于常规的、有长度限制的 varcharnvarchar 列,GetStreamGetBytes 都是不允许的。

能够调用 GetBytes 意味着我已经能够相当容易地构建自定义 SqlTextStream : Stream class,条件是从 nvarchar 读取时可能是最好以二的倍数读取字节,以免将字符撕成两半。

我查看了 SqlDataReader.GetColumnSchema() 提供的信息,但没有发现允许 GetBytes 反对 n/varchar(max) 结果的明显原因。我可能错过了一些东西,但是 GetColumnSchema 输出对于常规或(最大)字符数据似乎是相同的,除了长度。

有谁知道为什么 GetBytes 可以用于 n/varchar(max) 列?您认为 GetBytes 被允许是安全的吗?

下面是一些简单的测试代码:

public void Test()
{
    var cmd1 = "select c = 'getbytes permitted here' for json path";
    var cmd2 = "select c = cast('getbytes also permitted here' as nvarchar(max))";
    var cmd3 = "select c = cast('getbytes not permitted here' as nvarchar(32))";

    using (var con = new SqlConnection("data source=theDB; initial catalog=playground; integrated security=SSPI"))
        // switch between cmd1, cmd2 and cmd3 to see the different behaviour.
        using (var cmd = new SqlCommand(cmd1, con))
        {
            con.Open();

            using (var rdr = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess))
            {
                var o = rdr.GetColumnSchema();
                var buffer = new byte[128];
                rdr.Read();
                //System.IO.Stream s = rdr.GetStream(0); this is never permitted
                rdr.GetBytes(0, 0, buffer, 0, buffer.Length); // this is permitted for cmd1 and cmd2
            }
        }
}
  1. 这是 GetBytes 的底层代码(方法从第 1504 行开始,但下面的片段从第 1510 行开始):
            // don't allow get bytes on non-long or non-binary columns
            MetaType mt = _metaData[i].metaType;
            if (!(mt.IsLong || mt.IsBinType) || (SqlDbType.Xml == mt.SqlDbType)) {
                throw SQL.NonBlobColumn(_metaData[i].column);
            }
    
  2. SqlDataReader.GetBytes(Int32, Int64, Byte[], Int32, Int32) 的文档指出:

    No conversions are performed; therefore, the data retrieved must already be a byte array.

  3. 这种功能和行为上的差异很可能是由于数据在 SQL 服务器内部的存储方式所致。这就是为什么 true XML 被区别对待的原因,即使它是一个 blob 类型:有一种特殊的 SqlXml 二进制格式,通过将标签和属性减少到字典中来优化以减少从 duplication/repetition 膨胀(以及其他一些效率)。您的测试对 FOR JSONFOR XML 显示相同行为的原因应该是它们都 return NVARCHAR(MAX), FOR XML, TYPE returns true XML SqlXml 格式。