Streaming Results from Entity Framework Core and Web API Core – Part 2

Full source code available here.

Some time ago I wrote a post showing how to stream results from Entity Framework over Web API. This approach a few benefits – the results would not be materialized in the API code, a small amount of memory would be used irrespective of the size of the data returned, the results would being steaming as soon as possible and the speed of the request was faster than doing something like .ToList() or .ToListAsync().

In the example code I directly accessed the DbContext from the API controller, a reader of the blog got in touch to ask the database access code could be kept away from the API controller. This blog post shows a simple way of achieving this.

In the related post I had a method like this –

[HttpGet("streaming/")]
public IActionResult GetStreaming()
{
    IQueryable<Product> products = _salesContext.Products.AsNoTracking();
    return Ok(products);
}

The _salesContext was passed into the constructor of the ProductsController class using dependency injection and access directly.

In this post I’m going to pass a ProductsService into the ProductsController and use it to make the request to the database.

Here is how the controller now looks –

    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly ProductsService _productsService;

        public ProductsController(ProductsService productsService)
        {
            _productsService = productsService;
        }

        [HttpGet("streamingFromService/{count}")]
        public ActionResult GetStreamingFromService(int count)
        {
            return Ok(_productsService.GetStreaming(count));
        }

        [HttpGet("streamingFromServiceWithProjection/{count}")]
        public ActionResult GetStreamingFromServiceWithProjection(int count)
        {
            return Ok(_productsService.GetStreamingWithProjection(count));
        }

        [HttpGet("nonStreamingFromService/{count}")]
        public async Task<ActionResult> GetNonStreamingFromService(int count)
        {
            return Ok(await _productsService.GetNonStreaming(count));
        }
    }

The ProductService is very simple, it makes the relevant calls to the database and returns the Products or ProductModels. For good measure I’ve added Automapper to project the Product to ProductModel, separating the underlying data type from the one presented to the caller.

    public class ProductsService
    {
        private readonly SalesContext _salesContext;
        private readonly IMapper _mapper;

        public ProductsService(SalesContext salesContext, IMapper mapper)
        {
            _salesContext = salesContext;
            _mapper = mapper;
        }

        public IQueryable<Product> GetStreaming(int count)
        {
            IQueryable<Product> products = _salesContext.Products.OrderBy(o => o.ProductId).Take(count).AsNoTracking();
            return products;
        }

        public IQueryable<ProductModel> GetStreamingWithProjection(int count)
        {
            var productModels = _mapper.ProjectTo<ProductModel>(_salesContext.Products.OrderBy(o => o.ProductId).Take(count).AsNoTracking());
            return productModels;
        }

        public async Task<IList<Product>> GetNonStreaming(int count)
        {
            IList<Product> products = await _salesContext.Products.OrderBy(o => o.ProductId).Take(count).ToListAsync();
            return products;
        }
    }

Now, some people might be concerned about returning an IQueryable, if you are see this post by Mark Seemann https://blog.ploeh.dk/2012/03/26/IQueryableTisTightCoupling/ to start digging into the topic.

While writing this I came across what seems like a severe performance bug in Entity Framework Core 3.x, I’m working on another post which will cover this is detail.

Full source code available here.

Leave a Reply

Your email address will not be published. Required fields are marked *