Generic types in RegisterClassMap with parameters

非 Y 不嫁゛ 提交于 2020-12-15 05:55:28

问题


We want a generic approach for using CsvHelper. Depending the situation, the format of the CSV file is different. When the external system has all the required info more columns are present. I need to determine if the name of column is present in the CSV file (atm this is done in ResponseMap).

So we have an interface

List<T> ConvertTo<T, TMap>(string file, Dictionary<string, string> headers) where T : class
                                          where TMap : ClassMap<T>;

I'm calling it like this.

_csvRepository.ConvertTo<ResponseNgi, ResponseMap>(file, headers);

The dictionary contains the name of the columns depending the situation we are dealing with (e.g. all info = 13 columns, not all info = 7 columns)

The implementation of the ConvertTo method:

public List<T> ConvertTo<T, TMap>(string file, Dictionary<string,string> headers) where T : class
                            where TMap : ClassMap<T>
    {
        using (var reader = new StreamReader(file, Encoding.GetEncoding("iso-8859-1")))
        using (var csvReader = new CsvReader(reader))
        {
            var good = new List<T>();

            // Set up CSV Helper
            csvReader.Configuration.Delimiter = ";";
            csvReader.Configuration.IgnoreQuotes = true;
            csvReader.Configuration.HasHeaderRecord = true;
            csvReader.Configuration.HeaderValidated = null;
            csvReader.Configuration.MissingFieldFound = null;
            csvReader.Configuration.TrimOptions = TrimOptions.Trim;

            csvReader.Configuration.RegisterClassMap(new ResponseMap(headers)); 
            // csvReader.Configuration.RegisterClassMap<TMap>(); Can't pass dictionary like this

            // rest of the processing

            return good.ToList();
        }
    }

As you can see in the code snippet, I have explicitly used ResponseMap in order to be able to pass the headers. it doesn't feel right.

And the ResponseMap class, which is a subclass of ClassMap.

public class ResponseMap : ClassMap<ResponseNgi>
{
    public ResponseMap(Dictionary<string, string> headers)
    {
        Map(m => m.Street).Name(headers["street"]);
        Map(m => m.HouseNumber).Name(headers["house number"]);
        Map(m => m.PostalCode).Name(headers["postal code"]);
        Map(m => m.MunicipalitySection).Name(headers["municipality section"]);
        Map(m => m.Municipality).Name(headers["municipality"]);
        Map(m => m.StreetCode).Name(headers["street code"]);

        if (headers.ContainsKey("x"))
            Map(m => m.Xlambert).Name(headers["x"]).TypeConverter<CustomDoubleConverter>();

        if (headers.ContainsKey("y"))
            Map(m => m.YLambert).Name(headers["y"]).TypeConverter<CustomDoubleConverter>();

        if (headers.ContainsKey("z"))
            Map(m => m.ZLambert).Name(headers["z"]).TypeConverter<CustomDoubleConverter>();

        if (headers.ContainsKey("nrn"))
            Map(m => m.Nrn).Name(headers["nrn"]);

        if (headers.ContainsKey("source"))
            Map(m => m.Source).Name(headers["source"]);

        if (headers.ContainsKey("method"))
            Map(m => m.Method).Name(headers["method"]);

        Map(m => m.GeoadresId).Name(headers["geoadresId"]);
    }
}

How can I call RegisterClassMap with a generic type (TMap) and be able to map the CSV properly depending the situation?


回答1:


I have an extension method I use for mapping headers to a generic type. I have a pull request to allow the same extension method to pass in an Optional variable. Unfortunately, the optional part doesn't currently work, but you could set the 6 columns that aren't always used to Optional in your ResponseMap. You could also use the extension method with Dictionary<string, string> instead of IEnumerable<CsvMapping>.

public static List<T> ConvertTo<T, TMap>(string file, IEnumerable<CsvMapping> headers) where T : class
                    where TMap : ClassMap<T>
{
    using (var reader = new StreamReader(file, Encoding.GetEncoding("iso-8859-1")))
    using (var csvReader = new CsvReader(reader))
    {
        var good = new List<T>();

        // Set up CSV Helper
        csvReader.Configuration.Delimiter = ";";
        csvReader.Configuration.IgnoreQuotes = true;
        csvReader.Configuration.HasHeaderRecord = true;
        csvReader.Configuration.HeaderValidated = null;
        csvReader.Configuration.MissingFieldFound = null;
        csvReader.Configuration.TrimOptions = TrimOptions.Trim;

        var classMap = csvReader.Configuration.RegisterClassMap<TMap>();

        classMap.Map(headers);

        good = csvReader.GetRecords<T>().ToList();

        return good.ToList();
    }
}

public class CsvMapping 
{
    public string CsvHeaderName { get; set; }
    public string PropertyName { get; set; }
    public bool Optional { get; set; }
}

public static class CsvHelperExtensions
{
    public static void Map<T>(this ClassMap<T> classMap, IEnumerable<CsvMapping> csvMappings)
    {
        foreach (var mapping in csvMappings)
        {
            var property = typeof(T).GetProperty(mapping.PropertyName);

            if (property == null)
            {
                throw new ArgumentException($"Class {typeof(T).Name} does not have a property named {mapping.PropertyName}");
            }

            if (mapping.CsvHeaderName != null)
            {
                classMap.Map(typeof(T), property).Name(mapping.CsvHeaderName);
            }

            //// Pull request to CsvHelper to fix the Optional() method. 3/14/2019
            //if (mapping.Optional)
            //{
            //    classMap.Map(typeof(T), property).Optional();
            //}
        }
    }
}

public class ResponseMap : ClassMap<ResponseNgi>
{
    public ResponseMap()
    {
        Map(m => m.Street);
        Map(m => m.HouseNumber);
        Map(m => m.PostalCode);
        Map(m => m.MunicipalitySection);
        Map(m => m.Municipality);
        Map(m => m.StreetCode);

        Map(m => m.Xlambert).TypeConverter<CustomDoubleConverter>().Optional();
        Map(m => m.YLambert).TypeConverter<CustomDoubleConverter>().Optional();
        Map(m => m.ZLambert).TypeConverter<CustomDoubleConverter>().Optional();
        Map(m => m.Nrn).Optional();
        Map(m => m.Source).Optional();
        Map(m => m.Method).Optional();

        Map(m => m.GeoadresId);
    }
}


来源:https://stackoverflow.com/questions/59177675/generic-types-in-registerclassmap-with-parameters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!