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

时间:2018-10-19 00:34:00

标签: c# stream sqldatareader

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

我注意到,当查询返回json或xml时,SqlDataReader不允许使用GetStream()方法,根据文档要求:

  

'对列'c'的GetStream的无效尝试。 GetStream函数只能在Binary,Image,Udt或VarBinary类型的列上使用。'

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

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

使用常规的,受长度限制的varcharnvarchar列,不允许GetStreamGetBytes

能够调用GetBytes意味着我能够相当容易地构建自定义SqlTextStream : Stream类,但前提是从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 个答案:

答案 0 :(得分: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 SqlXml格式的XML。