WordPress database error: [Disk got full writing 'information_schema.(temporary)' (Errcode: 28 "No space left on device")]
SHOW FULL COLUMNS FROM `wp_options`

WordPress database error: [Disk got full writing 'information_schema.(temporary)' (Errcode: 28 "No space left on device")]
SHOW FULL COLUMNS FROM `wp_options`

Using IAsyncEnumerable to Stream Excel Exports in ASP.NET Core – Reliable Hosting ASP.NET Reviews
ASP.NET Hosting

Using IAsyncEnumerable to Stream Excel Exports in ASP.NET Core

An export endpoint that loads 100,000 rows into memory, allocates a massive byte [], and brings down your server under concurrent demand is something you’ve seen before. However, things don’t have to be this way.

In this post, I’ll demonstrate how to create Excel export endpoints that, without ever creating the entire dataset in memory, stream data straight from your database to the HTTP response.

The Problem with Traditional Approaches

Here’s a typical export endpoint using a popular Excel library:

app.MapGet("/export/orders", async (AppDbContext db) =>
{
    // Step 1: Load ALL data into memory
    var orders = await db.Orders
        .Where(o => o.Status == OrderStatus.Active)
        .ToListAsync();  // 100,000 rows → ~50 MB in memory

    // Step 2: Build in-memory workbook
    using var wb = new XLWorkbook();
    var ws = wb.Worksheets.Add("Orders");
    ws.Cell(1, 1).InsertData(orders);  // Another copy in the DOM

    // Step 3: Serialize to byte[]
    using var ms = new MemoryStream();
    wb.SaveAs(ms);
    byte[] result = ms.ToArray();  // Third copy: the final bytes

    return Results.File(result, "application/vnd.openxmlformats...");
});

That’s three copies of your data in memory simultaneously. Under 10 concurrent requests, you’re looking at 500+ MB of memory pressure. The GC will not be happy.

What We Want Instead

The ideal flow: database cursor → stream → HTTP response wire. Data flows through, never accumulating.

┌──────────┐     ┌──────────────┐     ┌──────────────┐
│ Database │ ──→ │ Excel Writer │ ──→ │ HTTP Response │
│ (yield)  │     │ (stream)     │     │ (chunked)     │
└──────────┘     └──────────────┘     └──────────────┘
     ↑                  ↑                    ↑
  One row at        Writes to          Client receives
  a time, async     response body      as it's generated
Plain text

This requires two things:

  • An IAsyncEnumerable<T> data source.
  • An Excel writer that accepts IAsyncEnumerable<T> and writes to a Stream.

Step 1: The Data Source (IAsyncEnumerable)

EF Core makes this straightforward with AsAsyncEnumerable():

async IAsyncEnumerable<OrderExportDto> StreamOrdersAsync(AppDbContext db)
{
    await foreach (var order in db.Orders
        .AsNoTracking()
        .Where(o => o.Status == OrderStatus.Active)
        .Select(o => new OrderExportDto
        {
            OrderNo = o.OrderNo,
            CustomerName = o.Customer.Name,
            Amount = o.Amount,
            CreatedAt = o.CreatedAt
        })
        .AsAsyncEnumerable())
    {
        yield return order;
    }
}

EF Core’s AsAsyncEnumerable() streams results using a database cursor — the driver fetches rows from the server in batches, and you process them one at a time. No ToList() means no full materialization.

Step 2: The Streaming Writer

The writer needs to accept an IAsyncEnumerable<T> and a Stream:

// The magic: write rows as they arrive from the async enumerator
public static async Task WriteAsync<T>(
    Stream output,
    IAsyncEnumerable<T> data,
    Action<ExportProfile<T>>? configure = null,
    CancellationToken cancellationToken = default)

Internally, it:

  • Opens a ZipArchive on the output stream.
  • Writes static parts ([Content_Types].xml, styles.xml, etc.).
  • Opens the sheet XML entry.
  • Iterates the IAsyncEnumerable<T>; for each row, writes the XML directly to the ZIP entry’s stream.
  • Closes the sheet entry, writes relationship files, and completes the ZIP.

The critical point: rows are written to the output stream as they arrive from the enumerator. If the output stream is the HTTP response body, the client starts downloading before the server has processed all rows.

Step 3: The Minimal API Endpoint

Putting it together:

app.MapGet("/export/orders", async (HttpContext ctx, AppDbContext db) =>
{
    ctx.Response.ContentType =
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    ctx.Response.Headers["Content-Disposition"] =
        "attachment; filename=orders.xlsx";

    await Xlsx.WriteAsync(
        ctx.Response.Body,                // Output: HTTP response stream
        StreamOrdersAsync(db),           // Input: IAsyncEnumerable<T>
        p => p
            .Sheet("Active Orders")
            .Column(x => x.OrderNo, c => c.WithName("Order #").WithWidth(20))
            .Column(x => x.Amount, c => c.WithFormat("$#,##0.00"))
            .WithFreezeHeader(),
        ctx.RequestAborted);             // Cancellation support

    // No return — response has been written directly to the stream
});

Now stream 100,000 rows, and your memory usage stays flat at approximately 1 MB regardless of dataset size.

Cancellation Support

Notice the ctx.RequestAborted token. If the client disconnects (browser tab closed, network error), the token cancels the operation:

await Xlsx.WriteAsync(
    ctx.Response.Body,
    StreamOrdersAsync(db),
    p => p.Sheet("Orders"),
    ctx.RequestAborted);  // Client disconnect → cancellation

The async enumerator respects the token, the ZipArchive closes cleanly (or at least doesn’t leave a corrupted ZIP), and server resources are freed.

Async Reading: The Other Direction

Import works the same way in reverse:

app.MapPost("/import/orders", async (HttpRequest req, AppDbContext db) =>
{
    await foreach (var order in Xlsx.ReadAsync<OrderImportDto>(req.Body))
    {
        db.Orders.Add(new Order
        {
            OrderNo = order.OrderNo,
            Amount = order.Amount,
            // ...
        });
    }

    await db.SaveChangesAsync();
    return Results.Ok();
});

ReadAsync<T> returns IAsyncEnumerable<T>, so you can await foreach and process rows one at a time — no ToList() between parsing and processing.

Memory Profile: Visual Comparison

Let’s compare memory behavior under load (10 concurrent requests, 50,000 rows each):

Approach Peak Memory Gen2 GC Events P99 Latency
Traditional (ToList + DOM) 480 MB 12 8.3 s
Streaming (IAsyncEnumerable + WriteAsync) 18 MB 0 2.1 s

The streaming approach simply doesn’t allocate enough to trigger Gen2 collections. The P99 latency improves because there’s no GC pause in the middle of request processing.

Benchmarks: 10,000 × 4 Columns (String)

Just to ground this in numbers, here’s a single-request comparison on .NET 10 (Apple M4):

Library Write Time Allocated
Streaming (IAsyncEnumerable path) 3.1 ms 0.98 MB
MiniExcel 11.9 ms 36.3 MB
EPPlus 40.2 ms 29.6 MB
ClosedXML 50.4 ms 84.4 MB

The streaming writer is approximately 4× faster than MiniExcel and uses 37× less memory — and this is for a single request. Under concurrent load, the gap widens dramatically because the traditional libraries multiply their allocation by the number of concurrent requests.

When NOT to Use This Approach

Streaming has real tradeoffs. Choose carefully:

Post-Write Cell Modification

Once a row is written, you can’t go back. You can’t merge cells that span rows you haven’t written yet (unless you pre-compute the range).

Formulas Referencing Forward Rows

SUM(A2:A10000) works because it references the range, not individual cells. But =A2+A3 works because both cells have been written when row 3 is processed.

Pivot Tables and Charts

These typically require the full dataset upfront. Generate them in a second pass or use a DOM-based library.

The Key Insight

IAsyncEnumerable<T> isn’t just a syntactical convenience — combined with a streaming writer, it fundamentally changes the memory model of your export pipeline. Your memory usage becomes O(1) relative to dataset size, not O(n).

If you’re generating Excel files in a web API, this pattern is worth adopting. The code in this article uses the Magicodes.IE.IO library (MIT), but the streaming principle applies regardless of which Excel library you choose.

Summary

Traditional Excel export implementations often load entire datasets into memory, resulting in high memory consumption, increased garbage collection activity, and poor scalability under concurrent load. By combining EF Core’s IAsyncEnumerable<T> with a streaming Excel writer, developers can create export pipelines that process rows incrementally and write directly to the HTTP response stream. This approach keeps memory usage constant regardless of dataset size, improves response times, reduces GC pressure, and enables highly scalable Excel exports in ASP.NET Core applications.

ASP.NET Core 10.0 Hosting Recommendation

HostForLIFE.eu
HostForLIFE.eu is a popular recommendation that offers various hosting choices. Starting from shared hosting to dedicated servers, you will find options fit for beginners and popular websites. It offers various hosting choices if you want to scale up. Also, you get flexible billing plans where you can choose to purchase a subscription even for one or six months.

WordPress database error: [Disk got full writing 'information_schema.(temporary)' (Errcode: 28 "No space left on device")]
SHOW FULL COLUMNS FROM `wp_postmeta`