Excel als Schnittstelle, nicht als Datenbank: EPPlus in modernen .NET-Anwendungen

Wenn der Mittelstand aus der Excel-Hölle rausgeht, will er nicht gleich SAP. Wie EPPlus aus deiner .NET-Anwendung eine ehrliche ERP-Light macht — plus offene Worte zur Lizenz und zu den Alternativen.

In jedem zweiten Erstgespräch sitzt nach 20 Minuten dieser Konflikt auf
dem Tisch:

Geschäftsführer: "Wir wollen weg von der Excel-Lawine. Das ist nicht
mehr tragfähig."

Buchhalterin: "Wir brauchen Excel. Damit arbeiten wir seit 15 Jahren."

Beide haben Recht. Und beide übersehen das Gleiche.

Die Excel-Hölle entsteht, wenn Excel als Datenbank missbraucht wird —
multi-User, multi-Version, mit Formel-Spaghetti die keiner mehr anfasst
(siehe der Kipppunkt-Post). Daraus
schließen viele: Excel muss weg. Aber das ist die falsche Lehre. Die
richtige ist:

Excel ist nicht das Problem. Excel ist die Schnittstelle.

Eine schlanke, eigene Anwendung — Daten in einer richtigen Datenbank,
Logik in C#, Auswertungen als API — verträgt sich erstaunlich gut mit
einer Buchhaltung die "in Excel arbeitet". Wenn Excel der I/O-Layer
deiner ERP-Light wird (Import, Export, Report-Format, Bulk-Edit), bleiben
beide Seiten in ihrem Komfort: die Anwendung hält die Wahrheit, Excel
ist die Brücke zur menschlichen Nutzung.

Das Werkzeug für diese Brücke heißt im .NET-Universum meistens
EPPlus.

Was EPPlus ist (und was nicht)

EPPlus ist eine Bibliothek zum Lesen und Schreiben von .xlsx-Dateien
direkt aus C#. Sie arbeitet auf dem OOXML-Format (Office Open XML, der
ZIP-Container den Excel seit 2007 verwendet) und gibt dir eine
Objekt-API auf die Sheets, Zellen, Formeln, Charts, Pivot-Tables,
bedingten Formatierungen und Daten-Validierungen. Du sprichst nicht mit
einem laufenden Excel-Prozess (was im Server-Kontext sowieso eine
schlechte Idee wäre) — du baust die Datei programmatisch.

Eine 8-Zeilen-Tour, weil's wichtiger ist als jede Architektur-Tirade:

using OfficeOpenXml;

// Seit EPPlus 5 Pflicht: License-Context vor erstem Zugriff setzen
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

using var package = new ExcelPackage(new FileInfo("kunden.xlsx"));
var ws = package.Workbook.Worksheets[0];

var kunden = new List<Kunde>();
for (int row = 2; row <= ws.Dimension.End.Row; row++)
{
    kunden.Add(new Kunde {
        Name   = ws.Cells[row, 1].Text,
        Email  = ws.Cells[row, 2].Text,
        Umsatz = ws.Cells[row, 3].GetValue<decimal>()
    });
}

Das ist nicht hübsch, aber es ist ehrlich. Du siehst was passiert,
du siehst woher die Daten kommen, du kannst debuggen. Wenn die
Buchhaltung sich beschwert dass Spalte C plötzlich leer ist, kannst du
in 30 Sekunden nachvollziehen warum.

Schreiben sieht spiegelbildlich aus:

using var package = new ExcelPackage();
var ws = package.Workbook.Worksheets.Add("Umsatz Juni 2026");

ws.Cells["A1"].LoadFromCollection(monatszahlen, PrintHeaders: true);
ws.Cells["A1:D1"].Style.Font.Bold = true;
ws.Cells["D2:D" + (monatszahlen.Count + 1)]
   .Style.Numberformat.Format = "#,##0.00 €";
ws.Cells[ws.Dimension.Address].AutoFitColumns();

await package.SaveAsAsync(outputStream);

Vier Zeilen Formatierung — fettes Header, EUR-Format auf der Umsatz-
Spalte, automatische Spaltenbreite. Das Ergebnis sieht nach
"die Buchhaltung hat das selbst gebaut" aus, nicht nach "ein
Programmierer hat hier was aus einer Anwendung exportiert"
. Genau das
ist der Punkt.

4 Use-Cases die in jedem KMU-ERP-Light auftauchen

1) Stammdaten-Import beim Einrichten

Der Kunde fängt mit deiner neuen Anwendung an und hat — natürlich — 2400
Kundendatensätze in einer Excel-Datei aus der bisherigen Welt. Statt
einer Migrations-Vereinbarung mit Stunden-Aufwand für deine Seite gibt's
einen Upload-Endpoint:

[HttpPost("/api/import/kunden")]
public async Task<IActionResult> Importiere(IFormFile datei, CancellationToken ct)
{
    using var stream = datei.OpenReadStream();
    using var package = new ExcelPackage(stream);
    var ws = package.Workbook.Worksheets[0];

    var importiert = 0;
    var fehler    = new List<string>();

    for (int row = 2; row <= ws.Dimension.End.Row; row++)
    {
        try
        {
            var kunde = new Kunde {
                Name   = ws.Cells[row, 1].Text.Trim(),
                Email  = ws.Cells[row, 2].Text.Trim(),
                Umsatz = ws.Cells[row, 3].GetValue<decimal>()
            };
            await _kunden.AddAsync(kunde, ct);
            importiert++;
        }
        catch (Exception ex)
        {
            fehler.Add($"Zeile {row}: {ex.Message}");
        }
    }

    await _kunden.SaveChangesAsync(ct);
    return Ok(new { importiert, fehler });
}

Der Kunde lädt seine Excel-Datei hoch, sieht "2387 importiert,
13 Fehler in Zeilen X, Y, Z"
, korrigiert die 13 Zeilen direkt in seiner
Datei, lädt nochmal hoch — fertig. Das ist UX die kein Custom-Web-Formular
schlägt.

2) Monats-Report-Export

Der Geschäftsführer will am Monatsende eine Tabelle. Nicht einen
"Dashboard-Link", nicht eine "Power BI-Ansicht". Eine Tabelle, die er
in den Anhang seiner Quartals-Vorstandssitzungs-Mail packen kann.

public async Task<byte[]> ErzeugeMonatsreport(int jahr, int monat)
{
    var daten = await _reports.LadeMonatsumsatz(jahr, monat);

    using var package = new ExcelPackage();
    var ws = package.Workbook.Worksheets.Add($"{jahr:0000}-{monat:00}");

    ws.Cells["A1"].LoadFromCollection(daten, PrintHeaders: true);

    // Header-Styling
    var header = ws.Cells["A1:E1"];
    header.Style.Font.Bold = true;
    header.Style.Fill.PatternType = ExcelFillStyle.Solid;
    header.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);

    // Zahlen-Format auf der Umsatz-Spalte
    ws.Cells[$"E2:E{daten.Count + 1}"]
       .Style.Numberformat.Format = "#,##0.00 €";

    // Summen-Zeile
    var summe = daten.Count + 2;
    ws.Cells[$"D{summe}"].Value = "Summe";
    ws.Cells[$"D{summe}"].Style.Font.Bold = true;
    ws.Cells[$"E{summe}"].Formula = $"SUM(E2:E{daten.Count + 1})";
    ws.Cells[$"E{summe}"].Style.Font.Bold = true;

    ws.Cells[ws.Dimension.Address].AutoFitColumns();
    return await package.GetAsByteArrayAsync();
}

Das SUM-Formel-Ding ist nicht Kosmetik: die Excel-Datei rechnet am
Empfänger-Rechner
weiter. Wenn der Geschäftsführer eine Zeile löscht
(weil sie zur Tochtergesellschaft gehört und nicht in den Vorstands-
Bericht soll), bleibt die Summe konsistent. Mit einem statischen
PDF-Export geht das nicht.

3) Bulk-Edit über Export-Edit-Import

Der schwierigste Use-Case zum Verkaufen, weil er aussieht wie ein Hack —
aber er funktioniert besser als jedes Web-Formular wenn jemand
200 Datensätze gleichzeitig ändern muss.

Workflow: User klickt in deiner Anwendung "Selektierte exportieren"
bekommt eine .xlsx-Datei mit allen 200 Zeilen + einer technischen
ID-Spalte. Bearbeitet in Excel (Suche-Ersetzen, Spalten-Operationen,
Filter, Sort — alles was Excel kann und kein Web-Tool je gut macht).
Lädt das Ergebnis wieder hoch. Anwendung erkennt die IDs, updated die
Datensätze, zeigt einen Diff-Report "100 unverändert, 87 geändert,
3 mit Konflikten — bitte prüfen"
.

Das ist ehrliche UX. Die Buchhalterin liebt es. Du sparst dir das
Bauen von einer Inline-Edit-Tabelle, Excel-Style-Filtern, Bulk-Operation-
Buttons. Excel macht das schon.

4) Template-basierte Dokumente

Auftragsbestätigung, Lieferschein, Stundenabrechnung — alles
Dokumente die in 95% der Fälle den gleichen Look haben sollen wie das
Word-Template das die Firma seit 2014 nutzt.

EPPlus kann eine vorhandene Excel-Datei öffnen und Zellen befüllen.
Statt das Layout im Code zu konstruieren, legst du
Templates/Auftragsbestaetigung.xlsx in dein Projekt — mit Logo, Adresse,
Layout, allem — und sagst dem Code nur:

using var package = new ExcelPackage(new FileInfo("Templates/Auftragsbestaetigung.xlsx"));
var ws = package.Workbook.Worksheets["Vorlage"];

ws.Cells["B5"].Value  = auftrag.Kunde.Name;
ws.Cells["B6"].Value  = auftrag.Kunde.Adresse;
ws.Cells["E5"].Value  = auftrag.Datum;
ws.Cells["E6"].Value  = auftrag.Auftragsnummer;

for (int i = 0; i < auftrag.Positionen.Count; i++)
{
    var row = 12 + i;
    ws.Cells[row, 1].Value = auftrag.Positionen[i].Bezeichnung;
    ws.Cells[row, 2].Value = auftrag.Positionen[i].Menge;
    ws.Cells[row, 3].Value = auftrag.Positionen[i].Einzelpreis;
}

package.SaveAs(outputStream);

Der Designer im Haus kann das Template anpassen ohne Code-Änderung. Du
hast keine Custom-PDF-Engine im Hintergrund, keine LaTeX-Geschichte,
keinen WeasyPrint-Container. Excel macht den Druck — und Excel kann
auch nach PDF exportieren wenn es sein muss.

Die ehrliche Lizenz-Geschichte

Hier muss ich gerade sein:

EPPlus-Version Lizenz Status
EPPlus 4.x LGPL Frei nutzbar (auch kommerziell), aber ohne Wartung seit 2020. API ist solide aber älter.
EPPlus 5–7 Polyform Noncommercial 1.0.0 Kommerziell kostenpflichtig — non-commercial gratis, in der Anwendung deines Mandanten brauchst du eine Lizenz.

Preis-Realität (Stand 2026): EPPlus 7 Commercial liegt zwischen ~300
und 500 EUR pro Entwickler und Jahr
. Im KMU-Projekt für eine
Anwendung mit ein, zwei Entwicklern ist das verkraftbar — aber kein
Selbstläufer den du nebenher zahlst.

Bevor du das einplanst, prüf' kurz ob du wirklich EPPlus brauchst.

Alternativen, wenn EPPlus zu schwer ist

Bibliothek Lizenz Stärke Schwäche
ClosedXML MIT Frei in allen Szenarien, lebendige Community, sauberer API-Stil Pivot-Tables nur rudimentär, weniger Chart-Optionen
NPOI Apache 2.0 Frei, kann auch alte .xls API stammt aus Java-Welt, fühlt sich in C# fremd an
OpenXML SDK MIT (Microsoft) Maximale Kontrolle, offizielle Library Sehr low-level — du arbeitest mit XML-Strukturen, nicht mit Excel-Konzepten

Mein pragmatischer Filter für KMU-ERP-Light-Projekte:

  • 80% der Fälle: ClosedXML reicht. Import, Export, Formatierung,
    Formeln, einfache Charts. MIT-Lizenz, keine Sorgen, gleiche
    Aufgabenklasse wie EPPlus.
  • Wenn du Pivot-Tables programmatisch brauchst, oder
    Conditional-Formatting-Komplexität, oder die volle Chart-API:

    EPPlus kaufen. Die 300 EUR/Jahr sind in einer Anwendung mit 100k+
    Projektvolumen Hintergrundrauschen.
  • Wenn du genau eine kleine Sache machen musst und der Performance-
    Footprint zählt:
    OpenXML SDK direkt. Ist Arbeit, aber die
    schlankste Lösung.

Was das für dein nächstes ERP-Projekt heißt

Drei konkrete Architektur-Punkte:

  1. Excel ist Eingang und Ausgang, nicht Persistenz. Daten leben in
    einer Postgres- oder SQL-Server-Tabelle mit Schema, Constraints und
    Migrations. Excel ist das Format, in dem Daten zu dir und von
    dir reisen. Niemals der Speicherort.

  2. Bau Import-Endpoints früh. Pro Stammdaten-Typ einen
    Upload-Endpoint, der mit dem Excel-Format zurechtkommt das die
    Buchhaltung sowieso schon hat. Das ist die schnellste Onboarding-
    Mechanik die es gibt — die User müssen ihre Daten nicht in deinem
    System nachbauen.

  3. Erzwinge nicht Web-UI wo Excel besser ist. Bulk-Edits,
    Spreadsheet-Style-Filter, Massen-Operations: Excel ist seit 35 Jahren
    im Tuning. Du kannst da im Browser nicht mithalten, und du musst
    es auch nicht.

Wenn das gut umgesetzt ist, hat der Geschäftsführer seine ERP-Light, die
Buchhalterin hat ihr Excel, und du hast eine Anwendung die nach 5 Jahren
noch wartbar ist.

Wenn ihr gerade an genau dieser Stelle steht und nicht sicher seid wie
ihr's aufsetzen sollt — 30 Minuten reichen meistens um zu klären welcher
der vier Use-Cases bei euch trägt und was die richtige Library-Wahl ist.

30 Min Klartext-Sparring

— Bernhard