如何在 FileHelpers 中使用动态 CSV 分隔符?

发布于 2024-12-24 21:41:40 字数 3126 浏览 4 评论 0原文

问题: 我需要读取 CSV 文件。 我使用 FileHelpers 库来实现此目的。

问题是我需要一个动态分隔符(用户定义),这意味着任何东西都可以作为分隔符(逗号、分号、制表符、换行符,还有其他任何东西)。

问题是,FileHelpers 在属性中定义了分隔符,这意味着在编译时。这使得动态地完成它变得不可能。

我能做的就是声明一个新类,它继承自一个基类,并在这个新类上设置分隔符。

[FileHelpers.DelimitedRecord(",")]
public class CommaCustomer : BaseCustomer
{

}

这样我只需为每个新的分隔符在基类中进行更改。 问题是,我不能(也不想)为每个可能的分隔符创建一个子类。

这是我到目前为止的代码:

using System;
using System.Data;
using System.IO;
//using FileHelpers;
//using FileHelpers.RunTime;


namespace Examples
{


    class MainClass
    {


        [STAThread]
        static void Main()
        {
            FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(typeof(SemicolonCustomer));

            // To read use:

            string str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\SemicolonCustomers.txt";
            //str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\CustomersDelimited.txt";
            SemicolonCustomer[] custs = (SemicolonCustomer[])engine.ReadFile(str);
            //Customer[] custs = (Customer[]) engine.ReadFile("yourfile.txt");


            foreach (SemicolonCustomer cli in custs)
            {
                Console.WriteLine();
                Console.WriteLine("Customer: " + cli.CustId.ToString() + " - " + cli.Name);
                Console.WriteLine("Added Date: " + cli.AddedDate.ToString("d-M-yyyy"));
                Console.WriteLine("Balance: " + cli.Balance.ToString());
                Console.WriteLine();
                Console.WriteLine("-----------------------------");
            } // Next cli

            Console.ReadKey();
            Console.WriteLine("Writing data to a delimited file...");
            Console.WriteLine();


            // To write use:
            //engine.WriteFile("myyourfile.txt", custs);


            //If you are using .NET 2.0 or greater is 
            //better if you use the Generics version:

            // FileHelperEngine engine = new FileHelperEngine<Customer>();

            // To read use (no casts =)
            // Customer[] custs = engine.ReadFile("yourfile.txt");

            // To write use:
            // engine.WriteFile("yourfile.txt", custs);

        } // End Sub Main


    } // End Class  MainClass


    //------------------------
    //   RECORD CLASS (Example, change at your will)
    //   TIP: Remember to use the wizard to generate this class
    public class BaseCustomer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, "ddMMyyyy")]
        public DateTime AddedDate;
    }


    [FileHelpers.DelimitedRecord(";")]
    public class SemicolonCustomer : BaseCustomer
    {

    }


    [FileHelpers.DelimitedRecord(",")]
    public class CommaCustomer : BaseCustomer
    {

    }


}

是否可以在运行时编译子类

[FileHelpers.DelimitedRecord(\"" + delimiter + "\")]
public class AnyDelimiterCustomer : BaseCustomer
{           
}

然后在代码中引用此运行时编译的类?

Question:
I need to read a CSV file.
I use the FileHelpers library to achieve this.

The problem is I need a dynamic delimiter (user defined), meaning anything can be delimiter (Comma, semicolon, tab, newline, but also anything else).

The problem is, FileHelpers defines the delimiter in an attribute, which means at compile-time. This makes it impossible to do it dynamically.

What I can do is declare a new class, which inherits from one base class, and set the delimiter on this new class.

[FileHelpers.DelimitedRecord(",")]
public class CommaCustomer : BaseCustomer
{

}

That way I only have to make changes in the base class for every new delimiter.
The problem is, this is I can't (and don't want to) create a child class for every possible delimiter.

This is the code I have so far:

using System;
using System.Data;
using System.IO;
//using FileHelpers;
//using FileHelpers.RunTime;


namespace Examples
{


    class MainClass
    {


        [STAThread]
        static void Main()
        {
            FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(typeof(SemicolonCustomer));

            // To read use:

            string str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\SemicolonCustomers.txt";
            //str = @"D:\Username\Desktop\FileHelpers_Examples_CSharp_VbNet\Data\CustomersDelimited.txt";
            SemicolonCustomer[] custs = (SemicolonCustomer[])engine.ReadFile(str);
            //Customer[] custs = (Customer[]) engine.ReadFile("yourfile.txt");


            foreach (SemicolonCustomer cli in custs)
            {
                Console.WriteLine();
                Console.WriteLine("Customer: " + cli.CustId.ToString() + " - " + cli.Name);
                Console.WriteLine("Added Date: " + cli.AddedDate.ToString("d-M-yyyy"));
                Console.WriteLine("Balance: " + cli.Balance.ToString());
                Console.WriteLine();
                Console.WriteLine("-----------------------------");
            } // Next cli

            Console.ReadKey();
            Console.WriteLine("Writing data to a delimited file...");
            Console.WriteLine();


            // To write use:
            //engine.WriteFile("myyourfile.txt", custs);


            //If you are using .NET 2.0 or greater is 
            //better if you use the Generics version:

            // FileHelperEngine engine = new FileHelperEngine<Customer>();

            // To read use (no casts =)
            // Customer[] custs = engine.ReadFile("yourfile.txt");

            // To write use:
            // engine.WriteFile("yourfile.txt", custs);

        } // End Sub Main


    } // End Class  MainClass


    //------------------------
    //   RECORD CLASS (Example, change at your will)
    //   TIP: Remember to use the wizard to generate this class
    public class BaseCustomer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, "ddMMyyyy")]
        public DateTime AddedDate;
    }


    [FileHelpers.DelimitedRecord(";")]
    public class SemicolonCustomer : BaseCustomer
    {

    }


    [FileHelpers.DelimitedRecord(",")]
    public class CommaCustomer : BaseCustomer
    {

    }


}

Is it somehow possible at runtime to compile a child class

[FileHelpers.DelimitedRecord(\"" + delimiter + "\")]
public class AnyDelimiterCustomer : BaseCustomer
{           
}

And then reference this runtime compiled class in code ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

狂之美人 2024-12-31 21:41:41

我刚刚意识到有一个 DelimitedFileEngine 可以用另一种方式解决您的问题。

您可以直接去

var engine = new DelimitedFileEngine(typeof(BaseCustomer));
engine.Options.Delimiter = ",";

似乎 BaseCustomer 需要用 [DelimitedRecord] 属性进行装饰,否则会引发异常,但分隔符会被提供给 的任何内容覆盖>engine.Options.Delimiter

以下示例使用标记为条分隔符的格式导入逗号分隔记录。

[DelimitedRecord("|")]
public class Format1
{
    public string Field1;           
    public string Field2;            
    public string Field3;            
    public string Field4;
}

static void Main(string[] args)
{
    var engine = new DelimitedFileEngine(typeof(Format1));
    // change the delimiter
    engine.Options.Delimiter = ","; 

    // import a comma separated record
    object[] importedObjects = engine.ReadString(@"a,b,c,d");

    foreach (object importedObject in importedObjects)
    {
        if (importedObject is Format1)
        {
            Format1 format1 = (Format1)importedObject;
            // process it (for example, check the values)
            Assert.AreEqual("a", format1.Field1);
            Assert.AreEqual("b", format1.Field2);
            Assert.AreEqual("c", format1.Field3);
            Assert.AreEqual("d", format1.Field4);
        }
    }
}

I just realized there is a DelimitedFileEngine which solves your problem another way.

You can just go

var engine = new DelimitedFileEngine(typeof(BaseCustomer));
engine.Options.Delimiter = ",";

It seems that BaseCustomer needs to be decorated with a [DelimitedRecord] attribute, otherwise an exception is raised but the delimiter is overridden by whatever is supplied to engine.Options.Delimiter.

The following example imports a comma delimited record using a format which is marked as bar delimited.

[DelimitedRecord("|")]
public class Format1
{
    public string Field1;           
    public string Field2;            
    public string Field3;            
    public string Field4;
}

static void Main(string[] args)
{
    var engine = new DelimitedFileEngine(typeof(Format1));
    // change the delimiter
    engine.Options.Delimiter = ","; 

    // import a comma separated record
    object[] importedObjects = engine.ReadString(@"a,b,c,d");

    foreach (object importedObject in importedObjects)
    {
        if (importedObject is Format1)
        {
            Format1 format1 = (Format1)importedObject;
            // process it (for example, check the values)
            Assert.AreEqual("a", format1.Field1);
            Assert.AreEqual("b", format1.Field2);
            Assert.AreEqual("c", format1.Field3);
            Assert.AreEqual("d", format1.Field4);
        }
    }
}
沧桑㈠ 2024-12-31 21:41:41

不,那不可能。

但是您可以使用 FileHelper DelimitedClassBuilder 构建动态文件解析器,您可以在运行时设置分隔符:

DelimitedClassBuilder dcb = new DelimitedClassBuilder("Name", 
                                 "Here goes your col separator");

// You have to build your field definitions by hand now
dcb.AddField("FieldName", typeof(decimal));
...

// build the engine
DelimitedFileEngine fileEngine = new DelimitedFileEngine(dcb.CreateRecordClass());

// read the file
dynamic[] data = fileEngine.ReadFile(filePath);

No thats not possible.

But you can use the FileHelper DelimitedClassBuilder to build a dynamic file parser where you can set the delimiter at runtime:

DelimitedClassBuilder dcb = new DelimitedClassBuilder("Name", 
                                 "Here goes your col separator");

// You have to build your field definitions by hand now
dcb.AddField("FieldName", typeof(decimal));
...

// build the engine
DelimitedFileEngine fileEngine = new DelimitedFileEngine(dcb.CreateRecordClass());

// read the file
dynamic[] data = fileEngine.ReadFile(filePath);
看春风乍起 2024-12-31 21:41:41

您可以使用运行时类。你有两个选择。 从字符串编译你的类

例如

// The class definition 
public string mClass =  
@"  
    [DelimitedRecord(""" + delimiter + @""")]
    public class BaseCustomer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, ""ddMMyyyy"")]
        public DateTime AddedDate;
    }
"; 

Type t = ClassBuilder.ClassFromString(mClass); 

FileHelperEngine engine = new FileHelperEngine(t); 

DataTable = engine.ReadFileAsDT("test.txt"); 

或者,你可以使用DelimitedClassBuilder 类。

DelimitedClassBuilder cb = new DelimitedClassBuilder("BaseCustomer", delimiter);

cb.AddField("CustId", typeof(int));
cb.LastField.TrimMode = TrimMode.Both;
cb.LastField.FieldNullValue = 0;
cb.AddField("Balance", typeof(Decimal));
cb.AddField("AddedDate", typeof(DateTime));

engine = new FileHelperEngine(cb.CreateRecordClass());

DataTable dt = engine.ReadFileAsDT("test.txt");

You can use runtime classes. You have two choices. Either compile your class from a string

For instance

// The class definition 
public string mClass =  
@"  
    [DelimitedRecord(""" + delimiter + @""")]
    public class BaseCustomer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, ""ddMMyyyy"")]
        public DateTime AddedDate;
    }
"; 

Type t = ClassBuilder.ClassFromString(mClass); 

FileHelperEngine engine = new FileHelperEngine(t); 

DataTable = engine.ReadFileAsDT("test.txt"); 

Or alternatively, you can use the DelimitedClassBuilder class.

DelimitedClassBuilder cb = new DelimitedClassBuilder("BaseCustomer", delimiter);

cb.AddField("CustId", typeof(int));
cb.LastField.TrimMode = TrimMode.Both;
cb.LastField.FieldNullValue = 0;
cb.AddField("Balance", typeof(Decimal));
cb.AddField("AddedDate", typeof(DateTime));

engine = new FileHelperEngine(cb.CreateRecordClass());

DataTable dt = engine.ReadFileAsDT("test.txt");
z祗昰~ 2024-12-31 21:41:41

这是可能的。
但只能通过将序列化类型移动到单独的程序集中。

像这样:

using System;
using System.Collections.Generic;
using System.Windows.Forms;


namespace FlaechenupdateScript
{


    static class Program
    {


        // http://www.codeproject.com/KB/cs/runtimecompiling.aspx
        private static System.Reflection.Assembly BuildAssembly(string code)
        {
            Microsoft.CSharp.CSharpCodeProvider provider =
               new Microsoft.CSharp.CSharpCodeProvider();
            System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler();
            System.CodeDom.Compiler.CompilerParameters compilerparams = new System.CodeDom.Compiler.CompilerParameters();

            string strLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string strBasePath = System.IO.Path.GetDirectoryName(strLocation);

            string strSerializationTypes = System.IO.Path.Combine(strBasePath, "SerializationTypes.dll");
            string strFileHelpersLocation = System.IO.Path.Combine(strBasePath, "FileHelpers.dll");

            compilerparams.ReferencedAssemblies.Add(strSerializationTypes);
            compilerparams.ReferencedAssemblies.Add(strFileHelpersLocation);

            compilerparams.GenerateExecutable = false;
            compilerparams.GenerateInMemory = true;
            System.CodeDom.Compiler.CompilerResults results =
               compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                System.Text.StringBuilder errors = new System.Text.StringBuilder("Compiler Errors :\r\n");
                foreach (System.CodeDom.Compiler.CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
                           error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                return results.CompiledAssembly;
            }
        } // End Function BuildAssembly


        public static Type GetClassType(Type tt, string strDelimiter)
        {
            string strFullTypeName = tt.FullName;
            string strTypeUniqueName = System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString();
            strTypeUniqueName = "_" + strTypeUniqueName.Replace("-", "_");

            string xx = @"
            namespace CrapLord
            {

                [FileHelpers.IgnoreFirst]
                [FileHelpers.IgnoreEmptyLines]
                [FileHelpers.DelimitedRecord(""" + strDelimiter + @""")]
                public class " + strTypeUniqueName + @" : " + strFullTypeName + @"
                {

                }

            }

            ";

            System.Reflection.Assembly a = BuildAssembly(xx);

            var o = a.CreateInstance("CrapLord." + strTypeUniqueName);
            Type t = o.GetType();

            //System.Reflection.MethodInfo mi = t.GetMethod("EvalCode");
            //var s = mi.Invoke(o, null);

            return t;
        }


        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //Application.EnableVisualStyles();
            //Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());

            Type t = GetClassType(typeof(Tools.Serialization.CSV.Customer), ",");

            //FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(typeof(SemicolonCustomer));
            FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(t);
            string str = "path/to/datafile";
            Tools.Serialization.CSV.Customer[] custs = (Tools.Serialization.CSV.Customer[])engine.ReadFile(str);
            //Customer[] custs = (Customer[]) engine.ReadFile("yourfile.txt");


            foreach (Tools.Serialization.CSV.Customer cli in custs)
            {
                Console.WriteLine();
                Console.WriteLine("Customer: " + cli.CustId.ToString() + " - " + cli.Name);
                Console.WriteLine("Added Date: " + cli.AddedDate.ToString("d-M-yyyy"));
                Console.WriteLine("Balance: " + cli.Balance.ToString());
                Console.WriteLine();
                Console.WriteLine("-----------------------------");
            } // Next cli



            Console.WriteLine(Environment.NewLine);
            Console.WriteLine(" --- Press any key to continue --- ");
            Console.ReadKey();
        }


    }


}

SerializationTypes Assembly:

using System;
using System.Collections.Generic;
using System.Text;


namespace Tools.Serialization.CSV
{

    //------------------------
    //   RECORD CLASS (Example, change at your will)
    //   TIP: Remember to use the wizard to generate this class
    public class Customer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, "ddMMyyyy")]
        public DateTime AddedDate;
    }


}

It is possible.
But only by moving the serialization type into a separate assembly.

Like this:

using System;
using System.Collections.Generic;
using System.Windows.Forms;


namespace FlaechenupdateScript
{


    static class Program
    {


        // http://www.codeproject.com/KB/cs/runtimecompiling.aspx
        private static System.Reflection.Assembly BuildAssembly(string code)
        {
            Microsoft.CSharp.CSharpCodeProvider provider =
               new Microsoft.CSharp.CSharpCodeProvider();
            System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler();
            System.CodeDom.Compiler.CompilerParameters compilerparams = new System.CodeDom.Compiler.CompilerParameters();

            string strLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string strBasePath = System.IO.Path.GetDirectoryName(strLocation);

            string strSerializationTypes = System.IO.Path.Combine(strBasePath, "SerializationTypes.dll");
            string strFileHelpersLocation = System.IO.Path.Combine(strBasePath, "FileHelpers.dll");

            compilerparams.ReferencedAssemblies.Add(strSerializationTypes);
            compilerparams.ReferencedAssemblies.Add(strFileHelpersLocation);

            compilerparams.GenerateExecutable = false;
            compilerparams.GenerateInMemory = true;
            System.CodeDom.Compiler.CompilerResults results =
               compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                System.Text.StringBuilder errors = new System.Text.StringBuilder("Compiler Errors :\r\n");
                foreach (System.CodeDom.Compiler.CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n",
                           error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                return results.CompiledAssembly;
            }
        } // End Function BuildAssembly


        public static Type GetClassType(Type tt, string strDelimiter)
        {
            string strFullTypeName = tt.FullName;
            string strTypeUniqueName = System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString() + System.Guid.NewGuid().ToString();
            strTypeUniqueName = "_" + strTypeUniqueName.Replace("-", "_");

            string xx = @"
            namespace CrapLord
            {

                [FileHelpers.IgnoreFirst]
                [FileHelpers.IgnoreEmptyLines]
                [FileHelpers.DelimitedRecord(""" + strDelimiter + @""")]
                public class " + strTypeUniqueName + @" : " + strFullTypeName + @"
                {

                }

            }

            ";

            System.Reflection.Assembly a = BuildAssembly(xx);

            var o = a.CreateInstance("CrapLord." + strTypeUniqueName);
            Type t = o.GetType();

            //System.Reflection.MethodInfo mi = t.GetMethod("EvalCode");
            //var s = mi.Invoke(o, null);

            return t;
        }


        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //Application.EnableVisualStyles();
            //Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());

            Type t = GetClassType(typeof(Tools.Serialization.CSV.Customer), ",");

            //FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(typeof(SemicolonCustomer));
            FileHelpers.FileHelperEngine engine = new FileHelpers.FileHelperEngine(t);
            string str = "path/to/datafile";
            Tools.Serialization.CSV.Customer[] custs = (Tools.Serialization.CSV.Customer[])engine.ReadFile(str);
            //Customer[] custs = (Customer[]) engine.ReadFile("yourfile.txt");


            foreach (Tools.Serialization.CSV.Customer cli in custs)
            {
                Console.WriteLine();
                Console.WriteLine("Customer: " + cli.CustId.ToString() + " - " + cli.Name);
                Console.WriteLine("Added Date: " + cli.AddedDate.ToString("d-M-yyyy"));
                Console.WriteLine("Balance: " + cli.Balance.ToString());
                Console.WriteLine();
                Console.WriteLine("-----------------------------");
            } // Next cli



            Console.WriteLine(Environment.NewLine);
            Console.WriteLine(" --- Press any key to continue --- ");
            Console.ReadKey();
        }


    }


}

SerializationTypes Assembly:

using System;
using System.Collections.Generic;
using System.Text;


namespace Tools.Serialization.CSV
{

    //------------------------
    //   RECORD CLASS (Example, change at your will)
    //   TIP: Remember to use the wizard to generate this class
    public class Customer
    {
        public int CustId;

        public string Name;
        public decimal Balance;
        [FileHelpers.FieldConverter(FileHelpers.ConverterKind.Date, "ddMMyyyy")]
        public DateTime AddedDate;
    }


}
原谅我要高飞 2024-12-31 21:41:41

也许您想使用 Microsoft 的 TextFieldParser .VisualBasic.FileIO 命名空间:

string[] fields;
string[] delimiter = new string[] { "|" };
using (Microsoft.VisualBasic.FileIO.TextFieldParser parser =
           new Microsoft.VisualBasic.FileIO.TextFieldParser(filename))
{
    parser.Delimiters = delimiter;
    parser.HasFieldsEnclosedInQuotes = false;
    while (!parser.EndOfData)
    {
        fields = parser.ReadFields();

        //Do what you need
    }
}

Maybe you want to use the TextFieldParser from Microsoft.VisualBasic.FileIO Namespace:

string[] fields;
string[] delimiter = new string[] { "|" };
using (Microsoft.VisualBasic.FileIO.TextFieldParser parser =
           new Microsoft.VisualBasic.FileIO.TextFieldParser(filename))
{
    parser.Delimiters = delimiter;
    parser.HasFieldsEnclosedInQuotes = false;
    while (!parser.EndOfData)
    {
        fields = parser.ReadFields();

        //Do what you need
    }
}
趴在窗边数星星i 2024-12-31 21:41:41

添加强制转换为我解决了问题 (FileHelpers V3.5.1)

var engine = new DelimitedFileEngine(typeof(BaseCustomer));
((FileHelpers.Options.DelimitedRecordOptions)engine.Options).Delimiter=",";

adding a cast solves the problem for me (FileHelpers V3.5.1)

var engine = new DelimitedFileEngine(typeof(BaseCustomer));
((FileHelpers.Options.DelimitedRecordOptions)engine.Options).Delimiter=",";

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文