DbDataReader and SQL Server Data Chunking

Written by Bill Boga
3
This post is days old.

TL;DR

Using ExecuteReader against SQL Server with a query using for xml or for json causes data to be chunked. A single call to ExecuteReader.Read() will not return the expected data (for anything larger than 2,033-characters). A solution is to loop the reader until ExecuteReader.Read() returns false and append each call’s result to a List, StringBuilder, or other preferred container. Alternatively, wrap the original SQL query in a select-statement. For more information on the problem, reference this Microsoft Support entry.

How we encountered this problem

Within RIMdev, some of our API responses were taking more than a second. This was mostly due to the amount of joined-tables needed to query the necessary data. However, trying to do this with EF just adds overhead–which will only increase as our datasets increase. So, we turned to our DBA who showed us SQL Server’s JSON capabilities. A query was constructed to give us the necessary fields and wrapped in a call ending with for json path, without_array_wrapper (more info. here). We initially tried throwing the executed query’s result right to JsonConvert.Deserialize, but kept getting JsonReader exceptions. We added a break-point and discovered our data was truncated. But, running the same query in SSMS gave us the expected result. So, we setup an experiment to loop the reader and append each iteration’s result into a list. When we looked at the final list, we saw multiple entries, and when combined, gave us the expected value.

What causes this?

Don’t know. I have looked at .NET-related code available on GitHub (i.e. SqlDataReader) and found nothing. I’m thinking it’s SQL Server which does something different for remote calls.

Example

using (var command = context.Database.GetDbConnection().CreateCommand())
{
    // Assume our Widgets table has thousands of records...
    command.CommandText = "select top 1 widgets = (select * from widgets for json path) from widgets for json path, without_array_wrapper;";
    command.Connection.Open();

    using (var reader = command.ExecuteReader())
    {
        if (reader.HasRows)
        {
            var results = new List<string>();

            while (reader.Read())
            {
                results.Add(reader.GetString(0));
            }

            var json = string.Join(string.Empty, results);

            var response = JsonConvert.Deserialize<object>(json);
        }
    }
}

More examples

A full-featured .NET Core 2.0 sample can be found here.

Suggested reading

Comments