How to stream data directly from the database through a 3 tier architecture












5














The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.



The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)



A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:



[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();

var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");

var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};

return response;
}


The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:



[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});

return response;
}


Some junk code simulates a forward only DataReader connection to a database etc.



private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();

table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));

for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}

using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}

public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}


When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.



Can this be done more sensibly? Are there any obvious flaws in this code?










share|improve this question
























  • Maybe checkout: stackoverflow.com/questions/23295119/…
    – Re Captcha
    Jun 6 '18 at 11:41
















5














The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.



The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)



A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:



[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();

var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");

var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};

return response;
}


The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:



[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});

return response;
}


Some junk code simulates a forward only DataReader connection to a database etc.



private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();

table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));

for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}

using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}

public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}


When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.



Can this be done more sensibly? Are there any obvious flaws in this code?










share|improve this question
























  • Maybe checkout: stackoverflow.com/questions/23295119/…
    – Re Captcha
    Jun 6 '18 at 11:41














5












5








5


1





The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.



The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)



A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:



[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();

var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");

var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};

return response;
}


The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:



[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});

return response;
}


Some junk code simulates a forward only DataReader connection to a database etc.



private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();

table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));

for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}

using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}

public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}


When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.



Can this be done more sensibly? Are there any obvious flaws in this code?










share|improve this question















The problem: generating a CSV file that is too large be stored in memory in a 3 tier architecture, without the complexity of saving the file to storage.



The solution: I've managed to write something that will stream directly from the database into the user's browser, through two WebApi controllers (simulating two tiers in this case)



A request is made to the front end WebApi, which makes a HttpClient call to the back end WebApi:



[HttpGet]
[Route("getFrontEndWebApi")]
public async Task<HttpResponseMessage> GetFrontEndWebApi()
{
var httpClient = new HttpClient();

var dataApiStream = await httpClient.GetStreamAsync("getBackEndWebApi");

var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
await dataApiStream.CopyToAsync(stream);
dataApiStream.Close();
stream.Close();
});

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = $"CrossingTheStreams.csv"
};

return response;
}


The back end WebApi compiles the CSV by iterating over an IEnumerable and writes it out to a stream:



[HttpGet]
[Route("getBackEndWebApi")]
public async Task<HttpResponseMessage> GetBackEndWebApi()
{
var results = ReadFromTable();
var response = Request.CreateResponse();

response.Content = new PushStreamContent(async (stream, content, context) =>
{
using (var writer = new StreamWriter(stream))
{
foreach (var result in results)
{
await writer.WriteLineAsync($"{result.Id},{result.SomeBullshit}");
}
}
});

return response;
}


Some junk code simulates a forward only DataReader connection to a database etc.



private IEnumerable<FishyFish> ReadFromTable()
{
var table = new DataTable();

table.Columns.Add("Id", typeof(int));
table.Columns.Add("SomethingElse", typeof(string));

for (int i = 0; i < 1000000; i++)
{
table.Rows.Add(new object { i, Guid.NewGuid().ToString() });
}

using (var reader = table.CreateDataReader())
{
do
{
if (reader.HasRows)
{
while (reader.Read())
{
yield return new FishyFish()
{
Id = reader.GetInt32(0),
SomeValue = reader.GetString(1)
};
}
}
} while (reader.NextResult());
}
}

public class FishyFish
{
public int Id { get; set; }
public string SomeValue { get; set; }
}


When you hit a breakpoint on yield return you can see that results have made it to the browser before the foreach has iterated over every value, thus streaming straight to the browser without compiling the file in memory.



Can this be done more sensibly? Are there any obvious flaws in this code?







c# asp.net-web-api






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Mar 8 '17 at 9:25

























asked Mar 7 '17 at 17:00









James Law

210129




210129












  • Maybe checkout: stackoverflow.com/questions/23295119/…
    – Re Captcha
    Jun 6 '18 at 11:41


















  • Maybe checkout: stackoverflow.com/questions/23295119/…
    – Re Captcha
    Jun 6 '18 at 11:41
















Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41




Maybe checkout: stackoverflow.com/questions/23295119/…
– Re Captcha
Jun 6 '18 at 11:41










1 Answer
1






active

oldest

votes


















0














Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi





share








New contributor




zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.


















    Your Answer





    StackExchange.ifUsing("editor", function () {
    return StackExchange.using("mathjaxEditing", function () {
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    });
    });
    }, "mathjax-editing");

    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "196"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f157178%2fhow-to-stream-data-directly-from-the-database-through-a-3-tier-architecture%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi





    share








    New contributor




    zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.























      0














      Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi





      share








      New contributor




      zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





















        0












        0








        0






        Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi





        share








        New contributor




        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.









        Backend API can send in chunk through streaming https://weblogs.asp.net/andresv/asynchronous-streaming-in-asp-net-webapi






        share








        New contributor




        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.








        share


        share






        New contributor




        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.









        answered 2 mins ago









        zubin joshi

        1




        1




        New contributor




        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.





        New contributor





        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.






        zubin joshi is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
        Check out our Code of Conduct.






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f157178%2fhow-to-stream-data-directly-from-the-database-through-a-3-tier-architecture%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Morgemoulin

            Scott Moir

            Souastre