如果发生单个读取异常,GetRecord<T> returns null - CSVHelper library .NET

GetRecord<T> returns null if a single reading exception occurs - CSVHelper library .NET

我正在使用 .NET 的 CSVHelper 库 https://github.com/JoshClose/CsvHelper

我有一张 class-map,如下所示:

public class MyMap : ClassMap<Product>
{
    Map(c => c.DateVariable).Name("NonDateColumn");
    // Other valid map statements
}

调用获取记录时,如果只有一个 map 语句失败或类型不匹配,则该行返回为空。

var record = reader.GetRecord<T>();

我正在寻找任何已成功映射的数据,以及要用于其余记录的默认值。

有办法实现吗?

我不认为有一种开箱即用的方法。但是,您可以创建自己的自定义转换器,如果它们无法解析数据,则 return 默认值。对于DateTime,我从DateTimeConverter中取出了GitHub project中的代码,如果它无法解析该字段,则将其修改为return DateTime.MinValue

public class Program
{
    static void Main(string[] args)
    {
        using (var stream = new MemoryStream())
        using (var writer = new StreamWriter(stream))
        using (var reader = new StreamReader(stream))
        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
        {
            writer.WriteLine("Id,NonDateVariable,Bar");
            writer.WriteLine("1,Not a date,bar value");
            writer.WriteLine("2,2020/07/16,another value");
            writer.Flush();
            stream.Position = 0;

            csv.Configuration.TypeConverterCache.AddConverter<DateTime>(new BadDateConverter());
            csv.Configuration.RegisterClassMap<FooClassMap>();

            var records = csv.GetRecords<Foo>();
        }
    }
}

public class Foo
{
    public int Id { get; set; }
    public DateTime DateVariable { get; set; }
    public string Bar { get; set; }
}


public class FooClassMap : ClassMap<Foo>
{
    public FooClassMap()
    {
        Map(m => m.Id);
        Map(m => m.DateVariable).Name("NonDateVariable");
        Map(m => m.Bar);
    }
}

public class BadDateConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        if (text == null)
        {
            return base.ConvertFromString(null, row, memberMapData);
        }

        var formatProvider = (IFormatProvider)memberMapData.TypeConverterOptions.CultureInfo.GetFormat(typeof(DateTimeFormatInfo)) ?? memberMapData.TypeConverterOptions.CultureInfo;
        var dateTimeStyle = memberMapData.TypeConverterOptions.DateTimeStyle ?? DateTimeStyles.None;

        if (memberMapData.TypeConverterOptions.Formats == null || memberMapData.TypeConverterOptions.Formats.Length == 0)
        {
            if (DateTime.TryParse(text, formatProvider, dateTimeStyle, out var d))
            {
                return d;
            }
            else
            {
                return DateTime.MinValue;
            }
        }
        else
        {
            if (DateTime.TryParseExact(text, memberMapData.TypeConverterOptions.Formats, formatProvider, dateTimeStyle, out var d))
            {
                return d;
            }
            else
            {
                return DateTime.MinValue;
            }
        }
    }
}

您还可以创建一个包装器转换器以在任何字段上使用。

public class NullIfErrorConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        var type = 
            (memberMapData.Member as PropertyInfo)?.PropertyType ??
            (memberMapData.Member as FieldInfo)?.FieldType ??
            throw new InvalidOperationException($"Invalid member type. '{memberMapData.Member.MemberType}'");
            
        var typeConverter = row.Configuration.TypeConverterCache.GetConverter(type);
        memberMapData.TypeConverter = typeConverter;
        try 
        {
            return typeConverter.ConvertFromString(text, row, memberMapData);
        }
        catch
        {
            return null;
        }
    }
}

确保将它附加到映射中的成员而不是全局的,因为它使用的是全局类型缓存。