Eine CSV-Preisliste mit VB.NET einlesen – 2024-Version

CSV-Preisliste mit VB.NET einlesen – Beispiel-App
CSV-Preisliste mit VB.NET einlesen – Beispiel-App (ä ist Absicht..)

Artikeldaten-Beispiel: Einlesen aus .csv-Datei

Hast Du Dich auch schonmal gefragt, wie Du eine CSV-Preisliste in VB.NET einlesen kannst? Gestern habe ich einen YouTube-Kommentar, bzw. einen Video-Wunsch eines Benutzers erhalten, wo es genau darum ging. Natürlich bin ich ein guter Klein-YouTuber *schnief* und setze die Wünsche meiner Zuschauer selbstverständlich um 😉!

💡 Wenn Du ein schnelles Code-Beispiel brauchst, scrolle einfach nach unten zum kompletten Code, oder den Downloads. Auch wenn dieses Beispiel auf VB.NET ausgelegt ist, kannst Du es selbstverständlich auch kinderleicht für C# verwenden.

Da der User unter dem VB-Video kommentiert hatte, dachte ich, ich mache es direkt passend – dennoch arbeite ich heutzutage fast nur noch mit C# (außer ein Kunde wünscht es anders, oder ich mache ein Video zu VB.NET).

Keine Lust auf Text? Einfach als Video schauen

Falls Du keine Lust hat, diesen Beitrag in Textform zu lesen, kannst Du natürlich auch zum passenden YouTube-Video von mir rüberschwenken. Einfach in das Rechteck hier drüber klicken und schon geht es los.

Gedanken, die man sich machen sollte: Objektorientierung

Bevor wir mit dem eigentlichen „Problem“, also der programmiertechnischen Herausforderung starten, setzen wir unsere Segel erstmal Richtung Datentypen. Wir machen uns also im ersten Schritt Gedanken darüber, mit welcher Art Daten, bzw. mit welchen Objekten wir arbeiten werden. Hier kommt bei einer Artikel-, bzw. Preisliste natürlich ein „Ding“ sofort in unseren Kopf: Der Artikel!

Eine letztendliche Auflistung dieser Artikel ist ja dann praktische unsere Preisliste im objektorientierten Sinne. Wenn Diese schön sauber gebaut ist, haben wir anschließend sogar noch die Möglichkeit, Sie zu filtern und weiterzuverarbeiten. Dazu kommen wir dann weiter unten..

Damit wir die Klasse allerdings gut strukturieren können (erneut – hier einfach gehalten), schauen wir uns im nächsten Schritt die Datei selbst an. Bringt ja schließlich nichts, mit Spekulationen zu programmieren, wir brauchen harte Fakten!

Die Preisliste.csv-Datei für VB.NET

Wenn wir an eine typische CSV-Datei denken, dann denkt man wohl an verschiedene Werte, Welche durch Kommata getrennt sind (naja, „comma separated values“ halt, gell?). Jede Zeile steht hierbei für einen jeweils eigenen Datensatz / Artikel.

Also ungefähr so:

Name,Preis
Artikel 1,12.95
Artikel 2,120.95
Artikel 3,1000

Man trifft allerdings häufiger auf so eine Variante hier an, also durch Semikola (ja, ist tatsächlich der Plural von Semikolon und hat nichts mit dem Getränk zu tun!) getrennte Spalten:

Name;Preis
Artikel 1;12.95
Artikel 2;120.95
Artikel 3;1000

Im Endeffekt ist uns das auch fast egal, denn dann verarbeiten wir die Spalten einfach nur durch ein anders getrenntes Zeichen – muss man aber trotzdem drauf achten. Ebenso wichtig ist hier das Encoding, es könnte zu Problemen mit Umlauten sowie Sonderzeichen führen. Hierbei bin ich ein Fan von UTF-8 :)!

Da wir die Daten nun kennen, bauen wir im nächsten Schritt einfach die Artikel-Klasse.

Die Artikel-Klasse

Fangen wir also nun mit der Erstellung einer passenden Klasse an. Beachte jedoch, dass ich Diese für dieses Beispiel hier klein und einfach gehalten habe. Je nach Anwendungsfall, müsstest Du die Klasse natürlich noch um die ein oder andere Eigenschaft / Funktionalität erweitern.

Lege also eine neue Datei im Projekt an und bezeichne Diese als „Artikel.vb“:

Public Class Artikel

    Public Property Name As String

    Public Property Preis As Decimal

End Class
public class Artikel
{

    public string Name { get; set; }

    public decimal Preis { get; set; }

}

Hierbei habe ich die beiden für mich wichtigsten Eigenschaften des Artikels gewählt:

  1. Name – natürlich ein String
  2. Preis – ein Decimal

Optionaler, leerer Konstruktor

Nachdem wir diese beiden Eigenschaften definiert haben, könntest Du eventuell einen expliziten, leeren Konstruktor anlegen. Auch wenn Dieser praktisch von Haus aus vorhanden und „KISS – Keep It Smart & Simple“ an der Tagesordnung ist, finde ich es so (persönlich) trotzdem sauberer. Allerspätestens wenn Du (wie häufig) Parameter bei der Erstellung übergeben möchtest, bist Du darauf sowieso angewiesen.

Erweitere also die Artikel-Klasse bei Bedarf um den parameterlosen Konstruktor:

' Rest der Klasse...

Sub New()
  
End Sub

' ...
// Rest der Klasse...

public Artikel()
{

}

// ...

Auf geht’s in die Form

Im nächsten Step hüpfen wir in das Formular, Welches uns lediglich einen Button und auch noch eine Tabelle (DataGridView) bereitstellen wird. Wenn wir auf den Button klicken, sollen die Artikeldaten aus der CSV-Datei verarbeitet und anschließend dargestellt werden. Schaue Dir hierzu ggf. noch einmal das Beitragsbild von ganz oben an und designe es nach Bedarf ähnlich.

Klicke nun einmal doppelt auf die Form, damit wir direkt einmal das DataGridView konfigurieren können. Wir stellen hierbei die automatische Generierung der Spalten aus, Diese legen wir selbst fest, weil wir später noch eine „lesbarere Spalte“ definieren wollen! Durch den Doppelklick (oder einem anderen Weg) solltest Du nun im Load-Ereignishandler des Formulars sein:

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'Dim enUsCulture = New CultureInfo("en-US")
        'Thread.CurrentThread.CurrentCulture = enUsCulture
        DataGridView1.AutoGenerateColumns = False
    End Sub

Beachte, dass die obigen beiden Anweisungen gebraucht werden, wenn Du in der CSV-Datei ein englisches Zahlenformat (also „49.95“ statt „49,95“) verwendest. Damit setzt Du die sogenannte CultureInfo auf „Englisch“.

Danach kommen wir zum wichtigen, gerade eben angesprochenen Punkt: Wir schalten die automatische Generierung von Spalten bei Zuweisung einer Datenquelle aus. Dies geht durch das Zuweisen von „False“ mit der „AutoGenerateColumns“-Eigenschaft. Falls Du die automatisch generierten Spalten behalten möchtest, kommentiere diese Zeile natürlich dementsprechend aus.

DataGridView-Spalten konfigurieren

Nun kannst Du die Spalten des DataGridViews (bei ausgeschalteter Auto-Generierung) manuell festlegen. Gehe dazu mit einem Klick auf Dein DataGridView und wähle im Eigenschaftsfenster (Designer!) die „Columns“-Eigenschaft. Verwende die 3 Punkte, damit sich ein Menü öffnet und pflege die erwünschten Spalten ein. Im Beispiel von oben sind das zwei: Name und Preis.

DataGridView Spalten-Konfiguration – CSV-Preisliste mit VB.NET  einlesen
DataGridView Spalten-Konfiguration – CSV-Preisliste mit VB.NET einlesen

Achte hier auf die Bezeichnung der Spalte (cName, cPreis z. B.) und wähle einen entsprechenden Kopf-Text. Das Wichtigste bei der Verwendung via Datenbindung ist hier, dass Du die „DataPropertyName“-Eigenschaft setzt. Diese muss auf den Namen der zu bindenden / darzustellenden Eigenschaft gesetzt werden. In unserem Fall ist das „Name“ und „Preis“ – siehe Eigenschaften der Artikel-Klasse.

Dies ist besonders relevant, wenn Du (wie ich) lieber die sauber als Euro-Wert formatierte Variante des Preises sehen möchtest. Ergänze hierzu die Artikel-Klasse um eine passende ReadOnly-Eigenschaft:

' restliche Artikel-Klasse..

    Public ReadOnly Property LesbarerPreis As String
        Get
            Return Preis.ToString("C2")
        End Get
    End Property

' ...

Achtung bei xx.xx und xx,xx!

Beachte hierbei, dass Du ggf. oben die CultureInfo geändert hast, er würde daher nun die englische Ausgabe verwenden. Erinnere Dich hierzu, dass wir die „Sprache“ global geändert/gesetzt haben, damit er die Zahlen aus der CSV-Datei richtig verarbeitet. Du könntest hier nun z. B. die deutsche CultureInfo mitgeben, damit dies hier funktioniert:

' restliche Artikel-Klasse...

    ' Shared - bitte nicht bei jedem Abruf der Property neu instanziieren 🙈
    Private Shared deutscheCultureInfo = New CultureInfo("de-DE")

    Public ReadOnly Property LesbarerPreis As String
        Get
            Return Preis.ToString("C2", deutscheCultureInfo)
        End Get
    End Property

' ....
// restliche Artikel-Klasse...    

    // Shared - bitte nicht bei jedem Abruf der Property neu instanziieren 🙈
    private static var deutscheCultureInfo = new CultureInfo("de-DE");

    public string LesbarerPreis
    {
        get
        {
            return Preis.ToString("C2", deutscheCultureInfo);
        }
    }

// ...

Erstelle im nächsten Schritt einen Handler für das Klick-Ereignis des Buttons und den dementsprechenden Code. Um Dich nicht direkt mit dem Code zu erschlagen, teile ich Ihn in verdauliche Aspekte auf.

Was ist zu tun – der Pseudo-Code

  • Unser Quell der Daten ist eine Datei -> ergo, wir brauchen Dateioperationen
  • Dateipfad zusammenstückeln -> woher lesen wir?
  • Datei einlesen -> Dateioperation durchführen
  • Wir möchten 0, 1, oder mehrere Artikel aus der Datei bekommen -> wird verwenden einen dementsprechenden Datentyp: Array / List
  • Die erste Zeile der Datei ist die Kopf-Zeile -> hier gibt es wichtige Informationen über die Struktur der Datei
  • Jede sonstige Zeile beinhaltet jeweils einen Artikel

Anhand der Fakten sähe unser (erstes) Vorhaben also pseudo-code-mäßig so aus:

- Lese Zeilen aus Datei
- Verarbeite die erste Zeile, um Basis-Informationen zu erhalten
- Lese und verarbeite die restlichen Zeilen und erstelle jeweils einen Artikel daraus
- Fügen den Artikel unserer objektorientierten / virtuellen Preisliste hinzu

Dies würde näher an .NET formuliert erstmal grob wie gleich folgend aussehen. Man beachte hier von Beginn an das „Async“ Schlüsselwort – wir haben sicherlich nicht vor eine synchrone und eventuell hängende IO-Operation durchzuführen. Stell Dir vor, die Datei wäre 10MB groß, dann freut man sich hier mit der synchronen Variante sicherlich nicht! Nun zum Code:

    Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click
        Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv")
        Dim zeilen = Await File.ReadAllLinesAsync(dateipfad)

        Dim artikelListe = New List(Of Artikel)()
        For Each zeile In zeilen
            Dim artikel = New Artikel()
            ' zeile verarbeiten und Artikel befüllen
            artikelListe.Add(artikel)
        Next

        ' Rest...

    End Sub
private async void btnPreislisteEinlesen_Click(object sender, EventArgs e)
{
    var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv");
    var zeilen = await File.ReadAllLinesAsync(dateipfad);

    var artikelListe = new List<Artikel>();
    foreach (var zeile in zeilen)
    {
        var artikel = new Artikel();
        // zeile verarbeiten und Artikel befüllen
        artikelListe.Add(artikel);
    }
}

Wenn alles so einfach wäre..

Grundsätzlich würde das obige Vorgehen vermutlich erstmal aushalten, aber jeder, der mal „echte Daten“ importiert hat, wird wissen, dass das leider nicht so einfach ist. Kunden, Exporte von System, usw. spielen einem häufig eben genau das aus, was man nicht erwartet, deswegen ist jede Art von Flexibilität willkommen!

Denke z. B. an die folgende Situation – wir erwarten:

Name;Preis
Artikel 1;12.95
Artikel 2;120.95
Artikel 3;1000

Es kommt aber eine getauschte Variante.. und stell‘ Dir dies nun mit 15 vertauschten Spalten vor 🤮..

Preis;Name
12.95;Artikel 1
120.95;Artikel 2
1000;Artikel 3

Im nächsten Schritt bauen wir daher eine Funktionalität ein, Welche von selbst erkennt, wo sich die jeweiligen Spalten befinden. Wir lesen also die Header-Spalte und schauen: „Wo befindet sich der Preis?“. Dies könnte man sogar noch weiter spinnen und mit Übersetzungen, Abkürzungen und Co. machen, aber gut, ich lasse die Kirche mal wieder im Dorf.

Verfeinerung – yummy!

Die Erkennung der korrekten Spalten (Indices) wäre dementsprechend in dem Moment angebracht, wenn wir in der ersten Zeile sind. Nun kannst Du auch sehen, warum ich eine For- und keine For-Each-Schleife genommen habe. Dies kannst Du selbstverständlich so machen, wie Du es für richtig hältst. Achte nur darauf, dass Du nicht etwa in jeder Iteration irgendwie so einen Quatsch wie „IndexOf“ machst, da er sonst pro Schleifendurchlauf nochmal alle Einträge durch muss.

Da wir uns die Positionen natürlich nicht in jedem Durchlauf erneut suchen möchten (und auch nicht können, da wir nicht „nochmal“ in Zeile 0/1 sind), initialisieren wir uns die Variablen außerhalb. Ich nehme hier bewusst die -1, da man so ggf. noch prüfen könnte, ob eine jeweilige Spalte überhaupt gefunden wurde.

' außerhalb der For-Schleife
        Dim nameIndex = -1
        Dim preisIndex = -1

' innerhalb der For-Schleife 
        Dim zeile = zeilen(i)
        Dim daten = zeile.Split(";")           
        Dim ersteZeile = i = 0
        If ersteZeile Then
            For j = 0 To daten.Length - 1
                Dim headerBezeichnung = daten(j)
                If headerBezeichnung = "Name" Then
                    nameIndex = j
                ElseIf headerBezeichnung = "Preis" Then
                    preisIndex = j
                End If
            Next
            Continue For
        End If
' .. weiterer Inhalt der For-Schleife

Verarbeiten eines jeweiligen Artikels / Datensatzes

Nun ist es an der Zeit, den Datensatz, also die Zeile eines jeweiligen Artikels zu verarbeiten. Wir kennen an dieser Stelle bereits die Positionen der jeweiligen Informationen können daher also direkt damit arbeiten. Hier eine Möglichkeit den Artikel zu befüllen, man könnte freilich auch einen passenden Konstruktor verwenden:

' Innerhalb der For-Schleife...
    Dim artikel = New Artikel()
    artikel.Name = daten(nameIndex)
    artikel.Preis = Convert.ToDecimal(daten(preisIndex))
    artikelListe.Add(artikel)
' ...
// Innerhalb der For-Schleife...
var artikel = new Artikel();
artikel.Name = daten(nameIndex);
artikel.Preis = Convert.ToDecimal(daten(preisIndex));
artikelListe.Add(artikel);
// ...
    Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click
        Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv")
        Dim zeilen = Await File.ReadAllLinesAsync(dateipfad)

        Dim nameIndex = -1
        Dim preisIndex = -1

        Dim artikelListe = New List(Of Artikel)
        For i = 0 To zeilen.Length - 1
            Dim zeile = zeilen(i)
            Dim daten = zeile.Split(";")

            Dim ersteZeile = i = 0
            If ersteZeile Then
                For j = 0 To daten.Length - 1
                    Dim headerBezeichnung = daten(j)
                    If headerBezeichnung = "Name" Then
                        nameIndex = j
                    ElseIf headerBezeichnung = "Preis" Then
                        preisIndex = j
                    End If
                Next
                Continue For
            End If

            Dim artikel = New Artikel()
            artikel.Name = daten(nameIndex)
            artikel.Preis = Convert.ToDecimal(daten(preisIndex))
            artikelListe.Add(artikel)
        Next
        DataGridView1.DataSource = artikelListe
    End Sub
private async void btnPreislisteEinlesen_Click(object sender, EventArgs e)
{
    var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv");
    var zeilen = await File.ReadAllLinesAsync(dateipfad);

    var nameIndex = -1;
    var preisIndex = -1;

    var artikelListe = new List<Artikel>();
    for (var i = 0; i <= zeilen.Length - 1; i++)
    {
        var zeile = zeilen(i);
        var daten = zeile.Split(";");

        var ersteZeile = i == 0;
        if (ersteZeile)
        {
            for (var j = 0; j <= daten.Length - 1; j++)
            {
                var headerBezeichnung = daten(j);
                if (headerBezeichnung == "Name")
                    nameIndex = j;
                else if (headerBezeichnung == "Preis")
                    preisIndex = j;
            }
            continue;
        }

        var artikel = new Artikel();
        artikel.Name = daten(nameIndex);
        artikel.Preis = Convert.ToDecimal(daten(preisIndex));
        artikelListe.Add(artikel);
    }
    DataGridView1.DataSource = artikelListe;
}

Darstellen im DataGridView

Nun fehlt nur noch ein letzter Schritt, um unseren verarbeiteten Artikeldaten in unserer Tabelle (DataGridView) darstellen zu können. Füge dazu einfach den gleich folgenden Code nach der For-Schleife ein. Vergiss allerdings nicht, dass Du die Konfiguration der Spalten oben tätigen solltest!

' For Schleife

' For Schleife Ende

' artikelListe = Deine Variable (vom Typ List(Of Artikel)), Welche die verarbeiteten Artikel beinhaltet
DataGridView1.DataSource = artikelListe

Kompletter Code

Hast Du es eilig? Kein Problem, kenne ich! Nimm Dir, was Du brauchst einfach hier weg, oder lade das Beispielprojekt weiter unten herunter. Bevor die Wörter-Polizei kommt: „Artikäl“ ist mit Absicht so geschrieben..

Die Preisliste.csv

Name;Preis;
Artikäl 1;15,95;
Artikel 2;49,95;
Artikel 3;120,95;

Die Artikel-Klasse

Public Class Artikel

    Public Property Name As String

    Public Property Preis As Decimal

    Public ReadOnly Property LesbarerPreis As String
        Get
            Return Preis.ToString("C2")
        End Get
    End Property

    Sub New()

    End Sub

End Class
public class Artikel
{
    public string Name { get; set; }

    public decimal Preis { get; set; }

    public string LesbarerPreis
    {
        get
        {
            return Preis.ToString("C2");
        }
    }

    public Artikel()
    {
    }
}

Der Form-Code

Imports System.IO

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'Dim enUsCulture = New CultureInfo("de-DE")
        'Thread.CurrentThread.CurrentCulture = enUsCulture
        DataGridView1.AutoGenerateColumns = False
    End Sub

    Private Async Sub btnPreislisteEinlesen_Click(sender As Object, e As EventArgs) Handles btnPreislisteEinlesen.Click
        Dim dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv")
        Dim zeilen = Await File.ReadAllLinesAsync(dateipfad)

        Dim nameIndex = -1
        Dim preisIndex = -1

        Dim artikelListe = New List(Of Artikel)
        For i = 0 To zeilen.Length - 1
            Dim zeile = zeilen(i)
            Dim daten = zeile.Split(";")

            Dim ersteZeile = i = 0
            If ersteZeile Then
                For j = 0 To daten.Length - 1
                    Dim headerBezeichnung = daten(j)
                    If headerBezeichnung = "Name" Then
                        nameIndex = j
                    ElseIf headerBezeichnung = "Preis" Then
                        preisIndex = j
                    End If
                Next
                Continue For
            End If

            Dim artikel = New Artikel()
            artikel.Name = daten(nameIndex)
            artikel.Preis = Convert.ToDecimal(daten(preisIndex))
            artikelListe.Add(artikel)
        Next
        DataGridView1.DataSource = artikelListe
    End Sub

End Class
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualBasic;

public class Form1
{
    private void Form1_Load(object sender, EventArgs e)
    {
        // Dim enUsCulture = New CultureInfo("de-DE")
        // Thread.CurrentThread.CurrentCulture = enUsCulture
        DataGridView1.AutoGenerateColumns = false;
    }

    private async void btnPreislisteEinlesen_Click(object sender, EventArgs e)
    {
        var dateipfad = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "preisliste.csv");
        var zeilen = await File.ReadAllLinesAsync(dateipfad);

        var nameIndex = -1;
        var preisIndex = -1;

        var artikelListe = new List<Artikel>();
        for (var i = 0; i <= zeilen.Length - 1; i++)
        {
            var zeile = zeilen(i);
            var daten = zeile.Split(";");

            var ersteZeile = i == 0;
            if (ersteZeile)
            {
                for (var j = 0; j <= daten.Length - 1; j++)
                {
                    var headerBezeichnung = daten(j);
                    if (headerBezeichnung == "Name")
                        nameIndex = j;
                    else if (headerBezeichnung == "Preis")
                        preisIndex = j;
                }
                continue;
            }

            var artikel = new Artikel();
            artikel.Name = daten(nameIndex);
            artikel.Preis = Convert.ToDecimal(daten(preisIndex));
            artikelListe.Add(artikel);
        }
        DataGridView1.DataSource = artikelListe;
    }
}

Downloads

Du brauchst ein schnelles Code-Beispiel, um alles in eigener Hand ausprobieren zu können? Dann lade es Dir hier einfach kostenlos das herunter!

Weiterführende Links

Vielleicht ist auch einer der folgenden Beiträge für Dich interessant:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert