Home / Performance / Lightning-Fast CSV Imports with Noundry Slurp
Performance

Lightning-Fast CSV Imports with Noundry Slurp

Import a million-row CSV into PostgreSQL in seconds. Slurp handles schema inference, type detection, bulk inserts, and intelligent indexing — from the CLI or your C# code.

G
Chef George
February 5, 2026 10 mins Beginner 631 views

The Problem

You've been handed a 2GB CSV file with 5 million rows of sales data. The business needs it in PostgreSQL by end of day. You could write a custom importer — parse the CSV, guess the column types, write CREATE TABLE statements, handle bulk inserts, add indexes — but that's a full day of work for a one-off task.

You need a tool that reads a CSV, figures out the schema automatically, and loads it into any database at maximum speed.

Ingredients

Noundry.Slurp

vlatest

High-performance CSV-to-database ingestion library

Noundry.Slurp.Tool

vlatest

CLI tool for one-command CSV imports

PostgreSQL

v17

Target database (also supports SQL Server, MySQL, SQLite)

Install the CLI for quick one-off imports, or the library for programmatic control.

Preparation

dotnet tool install --global Noundry.Slurp.Tool

2. Or add the library to your project

dotnet add package Noundry.Slurp

3. Verify the CLI is available

slurp --help

That's it. No database drivers to install separately — Slurp bundles connectors for PostgreSQL, SQL Server, MySQL, and SQLite.

The Code

Slurp works two ways: a CLI for quick imports, and a C# library for programmatic control.

Terminal (CLI Usage)
# Simplest possible import — SQLite, zero config
slurp sales.csv -p sqlite -d sales.db

# PostgreSQL with credentials
slurp sales.csv -p postgres -s localhost -d analytics -u admin --password secret

# SQL Server with custom table name and schema
slurp sales.csv -p sqlserver -s localhost -d warehouse -t raw_sales --schema staging

# Large file optimizations
slurp huge_export.csv -p postgres -d warehouse --batch-size 25000

# Drop and recreate table (fresh import)
slurp updated_data.csv -p postgres -d analytics --drop

# Semicolon-delimited European CSV
slurp eu_report.csv -p postgres -d reporting --delimiter ";"

# Skip auto-indexing for fastest possible load
slurp bulk_data.csv -p postgres -d staging --no-indexes
Services/CsvImportService.cs
using Noundry.Slurp;
using Noundry.Slurp.Configuration;

namespace MyApp.Services;

public class CsvImportService
{
    private readonly ILogger<CsvImportService> _logger;

    public CsvImportService(ILogger<CsvImportService> logger)
    {
        _logger = logger;
    }

    public async Task<ImportResult> ImportCsvAsync(
        string filePath, string tableName, CancellationToken ct = default)
    {
        var config = new SlurpConfiguration
        {
            Provider = "postgres",
            Server = "localhost",
            Port = 5432,
            Database = "analytics",
            Username = "app_user",
            Password = "secret",
            TableName = tableName,
            BatchSize = 10000,
            Parallel = true,
            CreateIndexes = true,
            DropTableIfExists = true
        };

        using var engine = new SlurpEngine(config);

        // Track progress
        engine.ProgressChanged += (_, e) =>
        {
            _logger.LogInformation(
                "Import progress: {Percent:F1}% ({Rows:N0} rows, {Speed:N0} rows/sec)",
                e.PercentComplete, e.RowsProcessed, e.RowsPerSecond);
        };

        var result = await engine.IngestAsync(filePath, ct);

        if (result.Success)
        {
            _logger.LogInformation(
                "Imported {Rows:N0} rows into {Table} in {Duration:F2}s ({Speed:N0} rows/sec)",
                result.RowsInserted, result.TableName,
                result.Duration.TotalSeconds, result.RowsPerSecond);
        }
        else
        {
            _logger.LogError("Import failed: {Error}", result.ErrorMessage);
        }

        return new ImportResult(
            result.Success,
            result.RowsInserted,
            result.Duration,
            result.TableName ?? tableName);
    }
}

public record ImportResult(
    bool Success, long RowsInserted, TimeSpan Duration, string TableName);
slurp-config.json
{
  "provider": "postgres",
  "server": "db.production.internal",
  "port": 5432,
  "database": "analytics",
  "username": "etl_user",
  "password": "${DB_PASSWORD}",
  "schema": "imports",
  "batchSize": 10000,
  "createIndexes": true,
  "dropTableIfExists": false,
  "parallel": true,
  "timeout": 600,
  "columnMappings": {
    "Customer Name": "customer_name",
    "Order Date": "order_date",
    "Unit Price": "unit_price"
  },
  "columnTypes": {
    "order_id": "BIGINT",
    "unit_price": "DECIMAL",
    "is_returned": "BOOLEAN"
  }
}

Plating

Your CSV import pipeline is production-ready:

  • Automatic schema inference detects integers, decimals, dates, booleans, GUIDs, and strings from data samples
  • Intelligent indexing auto-creates indexes on ID columns, email fields, timestamps, and foreign keys
  • Bulk insert performance uses native APIs per provider (PostgreSQL COPY BINARY, SqlBulkCopy, MySqlBulkCopy)
  • Parallel processing kicks in automatically for files over 10MB
  • Column mapping renames messy CSV headers to clean database column names
  • Type overrides let you force specific SQL types when inference isn't enough

Performance benchmarks:

Provider 1M Rows Speed
PostgreSQL ~8s 125K rows/sec
SQL Server ~12s 83K rows/sec
MySQL ~15s 67K rows/sec
SQLite ~20s 50K rows/sec

Next steps:

  • Create reusable config files for recurring imports
  • Schedule imports with a .NET BackgroundService
  • Combine with Noundry.Tuxedo to query the imported data with raw SQL

Was this recipe helpful?

G

Chef George

.NET Architect & Head Chef

25+ years crafting enterprise C# solutions. Creator of the Noundry platform. Passionate about clean architecture, cloud-agnostic deployments, and code that cooks.