Search code examples
c#csventity-framework-corecsvhelper

EF Core : duplicate values sent to database


My code:

using var context = new NorthwindContext();
        
var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
{
    Delimiter = ";",
    HeaderValidated = null,
    MissingFieldFound = null
};

using var reader = new StreamReader("csvfile.csv");
using var csv = new CsvReader(reader, csvConfig);

var records = csv.GetRecords<Employees>().ToList();

foreach (var item in records) 
     Console.WriteLine($"{item.FirstName} {item.LastName}"); //results as expected

context.AddRange(records);

await context.SaveChangesAsync();
await context.DisposeAsync();

Csv file:

FirstName;LastName
John;Doe

Results:

John Doe is saved twice in the database.

Expected results:

John Doe is saved only once in the database.


Solution

  • That was a strange one. I tried it and got the same duplicate results. I only got one John Doe if I created the records list in code without using CsvHelper. I finally got a clue when I noticed one of the records had a value for ReportsTo with the EmployeeId of the first record. Apparently CsvHelper's AutoMap is also creating an Employees record for the ReportsToNavigation property. I would think that is a possible bug in CsvHelper. My only solution would be to manually map the fields that you need.

    using var context = new NorthwindContext();
            
    var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        Delimiter = ";",
        HeaderValidated = null,
        MissingFieldFound = null
    };
    
    using var reader = new StreamReader("csvfile.csv");
    using var csv = new CsvReader(reader, csvConfig);
    
    csv.Configuration.RegisterClassMap<EmployeesMap>();
    
    var records = csv.GetRecords<Employees>().ToList();
    
    foreach (var item in records) 
         Console.WriteLine($"{item.FirstName} {item.LastName}"); //results as expected
    
    context.AddRange(records);
    
    await context.SaveChangesAsync();
    await context.DisposeAsync();
    
    public sealed class EmployeesMap : ClassMap<Employees>
    {
        public EmployeesMap()
        {
            Map(m => m.FirstName);
            Map(m => m.LastName);
        }
    }