I want to determine how to limit the memory usage inside a job which retrieves a blob from a local database and transfers it to a third party web service via chunks.
Using SqlDataReader, I appear to have two options:
- Create a method that uses GetBytes with an offset to retrieve part of a blob returning a byte[]. The caller of the method would then be responsible for making a web request to transfer this chunk.
- Create a method that uses GetStream and make multiple requests to ReadAsync to fill up a byte[] buffer, making a web request with this buffer until the document has been transferred.
I have a preference for option 1, because it limits the responsibility of the method, however if I call GetBytes with an offset, will it load the entire offset into memory or is sql server capable of just returning the small chunk requested? If I use option 2, then the method will have two responsibilities, loading a chunk from the database and making web requests to store the document elsewhere.
// option 1
public async Task<Tuple<int, byte[]>> GetDocumentChunk(int documentId, int offset, int maxChunkSize)
{
var buffer = new byte[maxChunkSize];
string sql = "SELECT Data FROM Document WHERE Id = @Id";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@Id", documentId);
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
if (await reader.ReadAsync())
{
int bytesRead = (int)reader.GetBytes(0, offset, buffer, 0, maxChunkSize);
return new Tuple<int, byte[]>(bytesRead, buffer);
}
}
}
}
return new Tuple<int, byte[]>(0, buffer);
}
//option 2
public async Task<CallResult> TransferDocument(int documentId, int maxChunkSize)
{
var buffer = new byte[maxChunkSize];
string sql = "SELECT Data FROM Document WHERE Id = @Id";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@Id", documentId);
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
using (Stream uploadDataStream = reader.GetStream(0))
{
CallResult callResult;
int bytesRead;
do
{
bytesRead = await uploadDataStream.ReadAsync(buffer, 0, maxChunkSize);
callResult = await MyWebRequest(documentId, buffer, bytesRead);
if (callResult != CallResult.Success)
{
return callResult;
}
} while (bytesRead > 0);
return callResult;
}
}
}
}
}