【问题标题】:SqlDataReader - GetStream vs GetBytes on (n)varchar vs v (n)varchar(max) or for xml / for jsonSqlDataReader - GetStream vs GetBytes on (n)varchar vs v (n)varchar(max) or for xml / for json
【发布时间】:2018-10-19 00:33:59
【问题描述】:

我一直致力于以流的形式提供对大型 SQL 结果的访问,特别是当查询使用 for json 或 for xml 子句时。

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

'对列 'c' 的 GetStream 尝试无效。 GetStream 函数只能用于 Binary、Image、Udt 或 VarBinary 类型的列。'

但是,它确实允许使用GetBytes()

返回数据为varchar(max)nvarchar(max) 时的行为相同。 GetStream() 不起作用,但 GetBytes() 会。

对于常规的、长度有限的 varcharnvarchar 列,既不允许使用 GetStream 也不允许使用 GetBytes

能够调用GetBytes 意味着我已经能够相当容易地构建一个自定义SqlTextStream : Stream 类,但前提是从nvarchar 读取时最好以2 的倍数读取字节,以免将字符撕成两半。

我查看了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
            }
        }
}

【问题讨论】:

  • 你能解释一下wrap up这个词的含义吗?这里的动机是什么?是性能吗?请澄清您的问题...
  • 我认为这是一个有趣但可能无法回答的问题;如果有的话,它会提示“那是什么编码?”的问题;我原以为GetCharsTextReader 会是在这里实现的更可靠的API...
  • 将“总结”更改为“提供访问权限”。动机是表现,是的。当我考虑通过 PushStreamContent 将流推送到 httpClients 时,我开始走这条路,中间可能有一个 GZipStream。但我想我不妨将数据访问流返回的代码从中分离出来,所以我创建了一个派生自流的数据访问类。我认为它在其他场景中也可能有用。
  • Marc - 是的,GetChars 或 GetTextReader 肯定会工作,即使结果不是(最大)类型,但由于它们会得到 char[],而不是 byte[],因此需要进行转换执行以将结果公开为流。不是很糟糕,而且我确实有一个 Stream.Read() 实现可以做到这一点 - 这实际上是我的第一个方法。但我很好奇并开始探索选项,这就是我遇到这种行为的原因。
  • @allmhuran - char[] 会更自然。当客户端数据库驱动程序将结果传送到 ADO.Net 时,我相当肯定任何文本都将被处理为 UTF-16。

标签: c# sql-server stream sqldatareader


【解决方案1】:
  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) 的文档指出:

    不执行任何转换;因此,检索到的数据必须已经是一个字节数组。

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

【讨论】:

    猜你喜欢
    • 2017-08-11
    • 2015-05-12
    • 2010-12-12
    • 2015-04-24
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 2011-11-26
    • 2011-12-07
    相关资源
    最近更新 更多