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()
可以。
对于常规的、有长度限制的 varchar
或 nvarchar
列,GetStream
和 GetBytes
都是不允许的。
能够调用 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
}
}
}
- 这是 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);
}
- SqlDataReader.GetBytes(Int32, Int64, Byte[], Int32, Int32) 的文档指出:
No conversions are performed; therefore, the data retrieved must already be a byte array.
- 这种功能和行为上的差异很可能是由于数据在 SQL 服务器内部的存储方式所致。这就是为什么 true XML 被区别对待的原因,即使它是一个 blob 类型:有一种特殊的 SqlXml 二进制格式,通过将标签和属性减少到字典中来优化以减少从 duplication/repetition 膨胀(以及其他一些效率)。您的测试对
FOR JSON
和 FOR XML
显示相同行为的原因应该是它们都 return NVARCHAR(MAX)
, 但 做 FOR XML, TYPE
returns true XML SqlXml 格式。
我一直在研究一种方法,以流形式提供对大型 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()
可以。
对于常规的、有长度限制的 varchar
或 nvarchar
列,GetStream
和 GetBytes
都是不允许的。
能够调用 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
}
}
}
- 这是 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); }
- SqlDataReader.GetBytes(Int32, Int64, Byte[], Int32, Int32) 的文档指出:
No conversions are performed; therefore, the data retrieved must already be a byte array.
- 这种功能和行为上的差异很可能是由于数据在 SQL 服务器内部的存储方式所致。这就是为什么 true XML 被区别对待的原因,即使它是一个 blob 类型:有一种特殊的 SqlXml 二进制格式,通过将标签和属性减少到字典中来优化以减少从 duplication/repetition 膨胀(以及其他一些效率)。您的测试对
FOR JSON
和FOR XML
显示相同行为的原因应该是它们都 returnNVARCHAR(MAX)
, 但 做FOR XML, TYPE
returns true XML SqlXml 格式。