使用 CSVHelper 动态映射嵌套对象

RHa*_*ris 6 c# csvhelper

我正在使用 CSVHelper(感谢 Josh Close)来读取一个很好用的 CSV 文件。我现在正在尝试使用它将该文件映射到一些内部类;但是,我映射的 CSV 因客户而异,但都需要映射到我的内部类。我需要允许客户定义 CSV 如何映射到我的 POCO 对象。

我将客户定义的映射存储为Dictionary<string,int>-- ["Firstname", 20],["Lastname",21],["Address.Line1",30], ["Address.Line2",31] 等。

我有一个动态映射类,它可以在运行时根据给定的映射进行动态映射。我的问题在于处理引用类型属性。这是我到目前为止所拥有的。

POCO 课程

public class Client
{
   public int Id {get; set;}
   public string Firstname {get; set;}
   public string Lastname {get; set;}
   public Address Address {get; set;}
   ...
}

public class Address
{
   public string Line1 {get; set;}
   public string Line2 {get; set;}
   public string City {get; set;}
   ...
}
Run Code Online (Sandbox Code Playgroud)

根据我在这里这里遇到的一些帖子,我想出了以下使用定义的映射来动态映射 CSV 的内容。

动态地图

public class BaseCSVMap<T> : ClassMap<T> where T : class
{
   public void CreateMap(Dictionary<string,int> mappings)
   {
      foreach(var mapping in mappings)
      {
         var propname = mapping.Key;
         var csvIndex = mapping.Value;

         var member = typeof(T).GetProperty(propname);
         Map(typeof(T), member).Index(csvIndex);
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用动态地图

var id = 2;  //Customer 2
var mappings = dataContext.Mappings.Where(m => m.id = id);  //Get customer 2's map
using(var reader = File.OpenText(@"c:\temp\testfile.csv"))
{
   var csv = new CsvReader(reader);
   csv.Configuration.HasHeaderRecord = true;  //hardcoded for now
   var map = new BaseCSVMap<Client>();
   map.CreateMap(mappings);
   csv.Configuration.RegisterClassMap(map);

   var records = csv.GetRecords<Client>();
}
Run Code Online (Sandbox Code Playgroud)

我在我的BaseCSVMap<T>类中添加了以下内容,如果我的所有引用属性都是字符串,这会很好用,但当属性是其他属性时效果不佳。

var member = typeof(T).GetProperty(propname);

//New code
//Mapping would look like ["Address.Line1",78]
if(member.GetType().IsClass)
{
   string exp = $"c.{propname}";
   var p = Expression.Parameter(typeof(T), "c");
   var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new [] {p}, null, exp);
   Map((Expression<Func<T,string>>)e).Index(csvIndex);
}
Run Code Online (Sandbox Code Playgroud)

我还寻找一种方法来利用 CSVHelper 提供的参考映射,但无法弄清楚如何以动态方式做到这一点。

寻找有关如何使用 CSVHelper 为引用类型定义动态映射的指导。

Dav*_*cht 0

我可以通过检查属性类型并根据属性类型创建动态 lambda 表达式来完成此操作。您需要确保检查类中的所有属性类型,包括nullable类型。

public class BaseCSVMap<T> : ClassMap<T> where T : class
{
    public void CreateMap(Dictionary<string, int> mappings)
    {
        foreach (var mapping in mappings)
        {
            var propname = mapping.Key;
            var csvIndex = mapping.Value;

            var param = Expression.Parameter(typeof(T), "x");
            var property = (MemberExpression)propname.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);
            
            if (property.Type == typeof(string))
            {
                var expression = Expression.Lambda<Func<T, string>>(property, param);
                Map(expression).Index(csvIndex);
            }
            else if (property.Type == typeof(int))
            {
                var expression = Expression.Lambda<Func<T,int>>(property, param);
                Map(expression).Index(csvIndex);
            }
            else if (property.Type == typeof(int?))
            {
                var expression = Expression.Lambda<Func<T, int?>>(property, param);
                Map(expression).Index(csvIndex);
            }
            else
            {
                throw new Exception("No mapping for type " + property.Type);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

仅供使用此功能的用户参考,当前版本中有一些更改CsvHelper 30.0.1

void Main()
{
    var mappings = new Dictionary<string, int>
    {
        {"Id", 0},
        {"FirstName", 1},
        {"LastName", 2},
        {"Address.Line1", 5},
        {"Address.Line2", 3},
        {"Address.City", 4}
    };
    using (var reader = new StringReader("Id,FirstName,LastName,Line2,City,Line1\n1,My,Name,Address2,Cincinnati,Address1"))
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        var map = new BaseCSVMap<Client>();
        map.CreateMap(mappings);
        csv.Context.RegisterClassMap(map);

        var records = csv.GetRecords<Client>().Dump();
    }
}
Run Code Online (Sandbox Code Playgroud)