Skip to Content
Course content

70: Working with CSV Data in C#

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember a junior dev on my team a few years back who spent an entire Tuesday debugging a "data misalignment" issue. He was importing a list of client addresses from a CSV file. Everything worked perfectly for the first hundred records, but then he hit a record for a company located in "Austin, Texas". Because he had used a simple .Split(',') to parse the lines, that one comma inside the city/state field shifted every subsequent piece of data one column to the right. The zip code ended up in the "Notes" field, and the "Notes" field was null. He spent hours convinced the source file was corrupted, only to realize his parser wasn't actually "CSV-aware."

CSV files look simple because they're just text, but the "Comma Separated Values" specification is deceptively tricky. You have to deal with quoted fields, escaped characters, and varying line endings. While you can try to write a complex Regular Expression to handle these edges, I strongly advise against it. In a professional environment, we use libraries that have already solved these edge cases so we can focus on the actual business logic.

The Trap of the Simple Split

If you're dealing with a tiny, controlled internal file where you know for a fact that no comma will ever appear inside a data field, File.ReadAllLines() combined with string.Split() is tempting. It's fast to write. But as soon as a user enters "Company, Inc." into a text box that eventually ends up in your CSV, your application will crash or, worse, silently corrupt your database.

// The "Danger Zone" approach - avoid this for production data
var lines = File.ReadAllLines("data.csv");
foreach (var line in lines)
{
    var columns = line.Split(','); // This is where the "Austin, Texas" bug happens
    var name = columns[0];
    var city = columns[1]; 
}

Real-world CSVs often wrap fields in double quotes ("Field, with comma") to protect the delimiter. A manual split completely ignores this logic. This is why I always reach for CsvHelper. It's the industry standard for C# and handles the RFC 4180 specification—the "gold standard" for CSVs—without you having to think about it.

Leveraging CsvHelper for Robustness

Once you've added the CsvHelper NuGet package to your project, the workflow shifts from manipulating strings to manipulating objects. Instead of worrying about which index in an array holds the "Price" value, you map the CSV columns directly to a C# class. This makes your code type-safe and significantly easier to maintain.

Here is how I usually set up a basic read operation. Notice how we wrap the StreamReader and CsvReader in using blocks to ensure the file handle is released immediately after we're done.

using System.Globalization;
using CsvHelper;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// ... inside a method ...
using (var reader = new StreamReader("products.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    // GetRecords lazily yields the objects as they are read
    var records = csv.GetRecords<Product>().ToList();
    
    foreach (var product in records)
    {
        Console.WriteLine($"{product.Name} costs {product.Price:C}");
    }
}

Mapping Data to Strong Types

In a perfect world, your CSV headers would exactly match your C# property names. In the real world, you'll often get files with headers like "Prod_ID" or "Unit Price (USD)". You don't have to rename your C# properties to match these ugly headers; you can use a ClassMap.

A map tells CsvHelper: "When you see the column named 'Prod_ID', put that value into the 'Id' property." This separates your internal data model from the external file format, which is a crucial boundary to maintain in any professional app.

public sealed class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Map(m => m.Id).Name("Prod_ID");
        Map(m => m.Name).Name("Product Name");
        Map(m => m.Price).Name("Unit Price (USD)");
    }
}

// To use the map:
using (var reader = new StreamReader("products.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    csv.Context.RegisterClassMap<ProductMap>();
    var records = csv.GetRecords<Product>().ToList();
}



📋 Practical Task

Exercise: Bookstore Inventory Import Tool

You have been tasked with building a small utility for a local bookstore. They provide you with a CSV file named inventory.csv. The catch is that their file is formatted inconsistently: some book titles contain commas, and the headers use non-standard naming.

The CSV Format:
Book_Title,Author_Name,ISBN_Code,Price_Per_Unit
"The Great Gatsby, Deluxe Edition",F. Scott Fitzgerald,9780743273565,15.99
"1984",George Orwell,9780451524935,12.50
"The Hobbit",J.R.R. Tolkien,9780547928227,20.00

Your Requirements:

  • Create a Book class with properties for Title, Author, Isbn, and Price.
  • Implement a BookMap class to map the CSV headers (e.g., Book_Title) to your class properties.
  • Write a program that reads the inventory.csv file using CsvHelper.
  • After loading the data, calculate and print the total value of the entire inventory (the sum of all prices) to the console.
  • Ensure your code handles the quoted titles correctly (which CsvHelper does by default, but you must implement it correctly).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.