C# 从SqlDataReader读取JSON字符串的最有效和最快速的方法

C# 从SqlDataReader读取JSON字符串的最有效和最快速的方法,c#,json,sqldatareader,C#,Json,Sqldatareader,我试图从SQL Server 2016数据库返回一个相当大的数据集(数千行),格式为JSON(使用SQL Server 2016的FOR JSON自动功能);但是,我在从SqlDataReader读取结果时遇到了问题 我之前将结果作为常规行加载到DataTable中,这非常有效(加载整个表大约需要10-15秒)。但是,如果我尝试使用相同的数据构建作为JSON返回的字符串,那么构建从DataReader返回的字符串需要几分钟的时间。我想补充一点,在这个查询中,我还返回了大量的二进制数据(我有Sql

我试图从SQL Server 2016数据库返回一个相当大的数据集(数千行),格式为JSON(使用SQL Server 2016的FOR JSON自动功能);但是,我在从SqlDataReader读取结果时遇到了问题

我之前将结果作为常规行加载到DataTable中,这非常有效(加载整个表大约需要10-15秒)。但是,如果我尝试使用相同的数据构建作为JSON返回的字符串,那么构建从DataReader返回的字符串需要几分钟的时间。我想补充一点,在这个查询中,我还返回了大量的二进制数据(我有SqlGeometry对象,我正试图从数据库中检索)。我从中返回的典型字符串有几个100k字符长,读卡器的读取速度非常慢

无论如何,我解析JSON的加载代码如下:

public static Task<string> ExecuteJsonReaderAsync(string connectionString, CommandType commandType, string commandText, SqlParameter[] oParams = null, int timeout = 30, CancellationToken token = default(CancellationToken))
    {
        return Task<string>.Factory.StartNew(() =>
        {
            var str = string.Empty;
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                using (var command = new SqlCommand(commandText, connection))
                {
                    command.CommandTimeout = timeout;
                    command.CommandType = commandType;

                    if (oParams?.Length > 0) command.Parameters.AddRange(oParams);

                    using (var reader = command.ExecuteReader(CommandBehavior.CloseConnection))
                    {
                        while (reader.Read())
                        {
                            str = $"{str}{reader[0]}";
                        }
                        reader.Close();
                    }    

                    return str;
                }
            }
        }, token);    
    }
public static Task ExecuteJsonReaderAsync(string connectionString,CommandType CommandType,string commandText,SqlParameter[]oParams=null,int timeout=30,CancellationToken=default(CancellationToken))
{
返回Task.Factory.StartNew(()=>
{
var str=string.Empty;
使用(var连接=新的SqlConnection(connectionString))
{
connection.Open();
使用(var命令=新的SqlCommand(commandText,connection))
{
command.CommandTimeout=超时;
command.CommandType=CommandType;
if(oParams?.Length>0)命令.Parameters.AddRange(oParams);
使用(var reader=command.ExecuteReader(CommandBehavior.CloseConnection))
{
while(reader.Read())
{
str=$“{str}{reader[0]}”;
}
reader.Close();
}    
返回str;
}
}
},代币);
}
我尝试了各种命令选项来加速它,包括CloseConnection、SequentialAccess、SingleResult,但都没有成功。为什么构建字符串比从相同数据加载数据表花费的时间要长得多,有没有更快的方法来实现这一点


我想这一定是我做错了什么,或者是我忽略了什么,我希望有人以前遇到过这个问题。有什么想法吗?

您的代码在每个循环中重新分配内存中的字符串变量。这对代码的性能是有害的。相反,该类有一个内部缓冲区,允许更少的内存重新分配,如果您知道数据的总长度,还可以控制该缓冲区的大小以避免重新分配

所以


更多关于

这里有几个问题:

  • 通过
    $“{str}{reader[0]}”
    使用字符串格式对GC的性能影响很大。您应该使用
    StringBuilder
    Append()
    方法
  • 您应该一直使用
    async
    ,就像这样:
  • 下面是我如何编写代码以获得更好的性能(通过async进行扩展,通过StringBuilder进行内存优化):

    public static async Task ExecuteJsonReaderAsync(string connectionString,CommandType CommandType,string commandText,SqlParameter[]oParams=null,int timeout=30,CancellationToken=default(CancellationToken))
    {
    var str=string.Empty;
    等待使用var connection=newsqlconnection(connectionString);
    等待连接。OpenAsync(令牌);
    等待使用var命令=新的SqlCommand(commandText,connection);
    command.CommandTimeout=超时;
    command.CommandType=CommandType;
    if(oParams?.Length>0)命令.Parameters.AddRange(oParams);
    var stringBuilder=新的stringBuilder(1024*1024);
    使用var reader等待=等待命令.ExecuteReaderAsync(CommandBehavior.CloseConnection,令牌);
    while(等待reader.ReadAsync(令牌))
    {
    追加(读取器[0]);
    }
    等待reader.CloseAsync();
    返回stringBuilder.ToString();
    }
    
    使用StringBuilder实例我认为您的问题可能是
    str=$“{str}{reader[0]}”。尝试使用
    StringBuilder
    并在while循环中附加到它。否则每次都会创建一个新字符串(字符串是不可变的)。只是一个与您的主要问题无关的备注,但不要使用
    Task.Factory.StartNew
    ,因为您可以一直使用异步/等待。考虑使用<代码> AuthEntueAdErasyCnc/代码>西里尔,设置这种方式的原因是B/C,该代码在别处用于返回可以通过AuthExeCudieAdErasyc使用的任务。这正是做这项工作的一部分。那么,字符串构建是什么导致它在while(reader.Read())循环中花费如此长的时间进行迭代呢?我不会想到使用不可变字符串会导致处理该循环花费那么长的时间。如果有一种更快的方法将所有结果转储到字符串变量中,那就太好了。默认情况下,如果json超过一定的长度,sql将返回多行。这是我不知道的一点,感谢您提出这一点。但是,数据的长度是不同的,所以我可以利用其中的一些数据,但无法分配总大小。我是否需要研究序列化选项以将数据读入sqlchars或sqlstring?必须有一种更快的方法将所有数据转储到单个字符串中。您不需要分配总大小。文件解释说
    // Set an initial capacity of 1MB
    StringBuilder str = new StringBuidler(1024*1024);
    while (reader.Read())
    {
        str.Append(reader[0].ToString());
    }
    
    ....
    return str.ToString();
    
            public static async Task<string> ExecuteJsonReaderAsync(string connectionString, CommandType commandType, string commandText, SqlParameter[] oParams = null, int timeout = 30, CancellationToken token = default(CancellationToken))
            {
                var str = string.Empty;
                await using var connection = new SqlConnection(connectionString);
    
                await connection.OpenAsync(token);
    
                await using var command = new SqlCommand(commandText, connection);
                command.CommandTimeout = timeout;
                command.CommandType = commandType;
    
                if (oParams?.Length > 0) command.Parameters.AddRange(oParams);
    
                var stringBuilder = new StringBuilder(1024 * 1024);
                await using var reader = await command.ExecuteReaderAsync(CommandBehavior.CloseConnection, token);
                while (await reader.ReadAsync(token))
                {
                    stringBuilder.Append(reader[0]);
                }
                await reader.CloseAsync();
    
                return stringBuilder.ToString();
            }