我正在使用 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 为引用类型定义动态映射的指导。
我可以通过检查属性类型并根据属性类型创建动态 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)