C#用户定义的CSV映射到POCO [英] C# User Defined CSV Mapping to POCO

查看:260
本文介绍了C#用户定义的CSV映射到POCO的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个从串行/ UDP / TCP源读取输入数据的系统。输入数据只是不同数据类型的CSV(例如DateTime,double,int,string)。示例字符串可能是:

  2012/03/23 12:00:00,1.000,23,信息,1.234 

我当前有(未测试的)代码,允许用户指定CSV列表中的哪个属性。



在上面的例子中,我将有一个对象:

  public class InputData 
{
public DateTime Timestamp {get; set;}
public double Distance {get; set;}
public int Meters {get ; set;}
public string描述{get; set;}
public double Height {get; set;}
}

现在在这个类中,我有一个方法来解析CSV字符串并填充属性。此方法还需要映射信息,因为不能保证CSV数据到达的顺序 - 由用户定义正确的顺序。



这是我的Mapping类:

  //这个一般类处理将CSV映射到对象
public class CSVMapping
{
//包含属性名(Key)和CSV索引(值)的字典
// 0基于索引
public IDictionary< string,int& Mapping {get;组; }
}



现在我的方法ParseCSV():

  //使用反射来解析CSV调查输入
public bool ParseCSV(string pCSV,CSVMapping pMapping)
{
if (pMapping == null)return false;
else
{
Type t = this.GetType();
IList< PropertyInfo> properties = t.GetProperties();
//分割CSV值
string [] values = pCSV.Split(new char [1] {','});
//为每个属性从CSV中设置其值
foreach(属性中的PropertyInfo prop)
{
if(pMapping.Mapping.Keys.Contains(prop.Name))
{
if(prop.GetType()== typeof(DateTime))
{
if(pMapping.Mapping [prop.Name]> = 0& pMapping.Mapping [prop.Name]< values.Length)
{
DateTime tmp;
DateTime.TryParse(values [pMapping.Mapping [prop.Name]],out tmp);
prop.SetValue(this,tmp,null);
}
}
else if(prop.GetType()== typeof(short))
{
if(pMapping.Mapping [prop.Name] = 0&& pMapping.Mapping [prop.Name]< values.Length)
{
double tmp;
double.TryParse(values [pMapping.Mapping [prop.Name]],out tmp);
prop.SetValue(this,Convert.ToInt16(tmp),null);
}
}
else if(prop.GetType()== typeof(double))
{
if(pMapping.Mapping [prop.Name] = 0&& pMapping.Mapping [prop.Name]< values.Length)
{
double tmp;
double.TryParse(values [pMapping.Mapping [prop.Name]],out tmp);
prop.SetValue(this,tmp,null);
}
}
else if(prop.GetType()== typeof(string))
{
if(pMapping.Mapping [prop.Name] = 0&& pMapping.Mapping [prop.Name]< values.Length)
{
prop.SetValue(this,values [pMapping.Mapping [prop.Name]],null);
}
}
}
}
return true;
}
}

现在我的问题:



我可能有几个类,将需要这个功能。实现一个泛型类或扩展类来为我解析是有益的吗?我的方法是一个正确的方式来解析CSV数据和popluate我的对象 - 或者有更好的方法来做这个?



我已经阅读其他问题动态解析CSV,但是所有处理在运行时之前已知的顺序,而我需要用户定义顺序。

解决方案

OleDB非常适合解析CSV数据,你不必使用反射。下面是使用OleDB类进行映射的主要思想:


  1. 用户定义一个映射(使用委托,流畅的接口或其他东西)

  2. Parser从CSV文件创建OleDbConnection,Adapter,Command并填充dataTable。

  3. 解析器从DataTable提取IDataRecords,您的Mapper需要从IDataRecord映射到对象。对于记录到对象映射的指导,我建议阅读ORM映射器的来源,如Dapper.NET,Massive,PetaPoco。

OleDb CSV解析:将csv加载到oleDB和force将所有推断的数据类型转换为字符串


$ b $ p

UPDATE 一个字符串,不用说,使用最简单和最简单的方法是更好。所以,对于问题:



实现泛型类 - 如果没有需要进一步提高解析(没有更多的字符串,没有更多的约束/功能在未来) d去寻找一个静态类,它接受对象,字符串和映射信息。它会有与你现在几乎相同的外观。这里有一些修改版本(可能无法编译,但应该反映一般的想法):

  public static class CSVParser 
{
public static void FOLPOCO(object poco,string csvData,CSVMapping mapping)
{
PropertyInfo [] relatedProperties = poco.GetType()。GetProperties()。Where(x => mapping。 Mapping.Keys.Contains(x))。ToArray();
string [] dataStrings = csvData.Split(',');
foreach(relatedProperties中的PropertyInfo属性)
SetPropertyValue(poco,property,dataStrings [mapping.Mapping [property.Name]]);
}

private static void SetPropertyValue(object poco,PropertyInfo property,string value)
{
//这里代码将类型更改为必要的..
property.SetValue(poco,value);
}
}

关于字符串类型值转换 - 有 Convert.ChangeType 方法来处理大多数情况。有布尔变量的特殊问题(当它给出0而不是假)。



对于数据填充 - 尽管反射被认为是慢,单个对象和很少使用它应该足够了,因为它很容易和简单。处理poco问题的通常方法是:运行时转换方法创建(使用反射被初始化,然后像任何其他方法一样被编译和调用) - 通常使用DynamicMethod,Expression Trees和类似的实现 - 有很多话题这里就这样;动态对象的使用(自C#4.0起可用) - 在哪里分配/获取变量,你不需要声明它;使用市场上可用的库(通常来自ORM系统,因为它们严重依赖于数据到对象的转换)。



就我个人而言,我会测量反射是否适合我的表现需求,并会继续解决问题。


I have a system that reads input data from a Serial/UDP/TCP source. The input data is simply a CSV of different datattypes (e.g. DateTime, double, int, string). An example string might be:

2012/03/23 12:00:00,1.000,23,information,1.234

I currently have (untested) code that allows the user to specify which value in the CSV list goes to which property of a POCO.

So in the above example, i would have a object like so:

public class InputData
{
 public DateTime Timestamp{get;set;}
 public double Distance{get;set;}
 public int Metres{get;set;}
 public string Description{get;set;}
 public double Height{get;set;}
} 

Now in this class, i have a method to parse a CSV string and populate the properties. This method also requires "Mapping" information, as there is no guarantee which order the CSV data will arrive in - it is up to the user to define the correct order.

This is my Mapping class:

//This general class handles mapping CSV to objects
public class CSVMapping
{
    //A dictionary holding Property Names (Key) and CSV indexes (Value)
    //0 Based index
    public IDictionary<string, int> Mapping { get; set; }
}

Now my method ParseCSV():

//use reflection to parse the CSV survey input
public bool ParseCSV(string pCSV, CSVMapping pMapping)
{
    if (pMapping == null) return false;
    else
    {
        Type t = this.GetType();
        IList<PropertyInfo> properties = t.GetProperties();
        //Split the CSV values
        string[] values = pCSV.Split(new char[1] { ',' });
        //for each property set its value from the CSV
        foreach (PropertyInfo prop in properties)
        {
            if (pMapping.Mapping.Keys.Contains(prop.Name))
            {
                if (prop.GetType() == typeof(DateTime))
                {
                    if (pMapping.Mapping[prop.Name] >= 0 && pMapping.Mapping[prop.Name] < values.Length)
                    {
                        DateTime tmp;
                        DateTime.TryParse(values[pMapping.Mapping[prop.Name]], out tmp);
                        prop.SetValue(this, tmp, null);
                    }
                }
                else if (prop.GetType() == typeof(short))
                {
                    if (pMapping.Mapping[prop.Name] >= 0 && pMapping.Mapping[prop.Name] < values.Length)
                    {
                        double tmp;
                        double.TryParse(values[pMapping.Mapping[prop.Name]], out tmp);
                        prop.SetValue(this, Convert.ToInt16(tmp), null);
                    }
                }
                else if (prop.GetType() == typeof(double))
                {
                    if (pMapping.Mapping[prop.Name] >= 0 && pMapping.Mapping[prop.Name] < values.Length)
                    {
                        double tmp;
                        double.TryParse(values[pMapping.Mapping[prop.Name]], out tmp);
                        prop.SetValue(this, tmp, null);
                    }
                }
                else if (prop.GetType() == typeof(string))
                {
                    if (pMapping.Mapping[prop.Name] >= 0 && pMapping.Mapping[prop.Name] < values.Length)
                    {
                        prop.SetValue(this, values[pMapping.Mapping[prop.Name]], null);
                    }
                }
            }
        }
        return true;
    }
}

Now for my question:

I potentially have several classes that will require this functionality. Would it be beneficial to implement a generic class or an extension class to do the parsing for me? Is my method a sound way to parse CSV data and popluate my object - or is there a better way to do this?

I have read other questions on dynamically parsing CSV, but all deal with the order being known before runtime, whereas i require the user to define the order.

解决方案

OleDB is great at parsing CSV data and you don't have to use reflection for it. Here's the main idea for mapping with OleDB classes:

  1. User defines a mapping (using delegate, fluent interface or something) and it gets into the Dictionary in your Mapper class.
  2. Parser creates a DataTable and inserts columns from mapper
  3. Parser creates OleDbConnection, Adapter, Command and fills dataTable from CSV file in correct types.
  4. Parser extracts IDataRecords from DataTable and your Mapper needs to map from IDataRecord to objects. For guidance on record-to-object mapping I'd recommend reading sources of ORM mappers like Dapper.NET, Massive, PetaPoco.

OleDb CSV parsing: Load csv into oleDB and force all inferred datatypes to string

UPDATE

Since there's only one string, it goes without saying that using easiest and simplest approach is better. So, for the questions:

Implement generic class - if there's no need to further advance parsing (no more string, no more constraints/features in the future), I'd go for a static class that takes object, string and mapping information. It'd have almost the same look as yours does right now. Here's somewhat modified version (may not compile, but should reflect the general idea):

public static class CSVParser
{
    public static void FillPOCO(object poco, string csvData, CSVMapping mapping)
    {
        PropertyInfo[] relevantProperties = poco.GetType().GetProperties().Where(x => mapping.Mapping.Keys.Contains(x)).ToArray();
        string[] dataStrings = csvData.Split(',');
        foreach (PropertyInfo property in relevantProperties)
            SetPropertyValue(poco, property, dataStrings[mapping.Mapping[property.Name]]);
    }

    private static void SetPropertyValue(object poco, PropertyInfo property, string value)
    {
        // .. here goes code to change type to the necessary one ..
        property.SetValue(poco, value);
    }
}

Regarding the string to typed value conversion - there's Convert.ChangeType method that handles most of the cases. There's particular problem with boolean variables (when it's given "0" instead of "false") though.

As for data population - though reflection is said to be slow, for single objects and seldom usages it should suffice since it's easy and simple. Usual methods for dealing with problem of poco population are: run-time conversion method creation (that uses reflection to be initialized and then is compiled and called like any other method) - usually implemented using DynamicMethod, Expression Trees and similar - there's plenty of topic here on so; usage of dynamic objects (available since C# 4.0) - where to assign/get variable you don't need to declare it; use available libraries on the market (usually from the ORM systems, since they rely heavily on data-to-object conversion).

Personally, I'd measure if reflection is suitable for my performance needs and would progress forward pass the problem.

这篇关于C#用户定义的CSV映射到POCO的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆