`

C#读取Excel数据动态生成对象并进行序列化

阅读更多

      C#读取Excel数据动态生成对象并进行序列化

       

       由于工作需要,要把Excel数据(格式如下图)读取出来并动态创建类,并利用数据去实例化,然后在进行序列化存储。          

        要读取Excels数据就必须了解Excel的存储结构和存储方法,才能进行读取操作,从参考⑨+1中可知,.xlsx是一组.xml文件的集合,可以把.xlsx后缀名改成.zip,然后在打开就可以看到,既然是这样我们就可以从解析xml角度去读取xlsx的数据,可以参考⑨+1的解决办法。

        


 Excel的数据有两个作用:

1)动态创建类

上图要创建类为:

public class DynamicClass
{
       public string id;
       public string name;
       public string chapter_x;
       public unit x;
       public unit y;
}

 2)实例化对象

然后Value的每一行都是用来实例化类DynamicClass的对象。

 

       因此,首要任务是对Excel文件进行读写,我google了下,发现读取Excel数据大概有三种方法:

               1)采用OleDB读取文件

                2)引用com组件:Microsoft.Office.INterop.Excel.dll,读取文件(本文就是采取这种方法的)

                3)其他格式读取,如转成CSV或Zip进行读取

       当然还有其他利用第三方包的方法。

        方法1)读取效率高,在C#使用OleDb读取Excel,生成SQL语句使用的就是这个方法,但是方法1)只能读取单元格的“正文”数据,像批注等不能读取到(没有看到相关api)。

        我这里采用的是2)的方法且参考②中代码进行改装的,把读取Excel的数据保存在StringBuilder classSource,objectData中,classSource用来动态创建类的“源代码”,objectData则是把所有对象数据存储起来。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using Excel = Microsoft.Office.Interop.Excel;

namespace ReadXlsxData
{
    static class ParseXlsx
    {
        public static Excel.Application m_ExcelFile = new Excel.Application();
        public static StringBuilder classSource;
        public static StringBuilder objectData;
        public static void CloseExcelApplication()
        {
            m_ExcelFile.Quit();
            m_ExcelFile = null;
        }
        public static void ReadExcelFile(string excelFilePath)
        {
            classSource = new StringBuilder(); ;
            objectData = new StringBuilder();
            Excel._Workbook m_Workbook;
            Excel._Worksheet m_Worksheet;
            object missing = System.Reflection.Missing.Value;
            Console.WriteLine("excelFilePath:" + excelFilePath);
            m_ExcelFile.Workbooks.Open(excelFilePath, missing, missing, missing, missing, missing, missing, missing, missing, missing
                , missing, missing, missing, missing, missing);
            m_ExcelFile.Visible = false;
            m_Workbook = m_ExcelFile.Workbooks[1];
            m_Worksheet = (Excel.Worksheet)m_Workbook.ActiveSheet;
            int clomn_Count = m_Worksheet.UsedRange.Columns.Count;
            int row_Count = m_Worksheet.UsedRange.Rows.Count;
            classSource.Append("using System;\n");
            classSource.Append("[Serializable]\n");
            classSource.Append("public   class   DynamicClass \n");
            classSource.Append("{\n");
            string propertyName,propertyType;
            for (int j = 2; j < clomn_Count + 1; j++)
            {
                propertyName = ((Excel.Range)m_Worksheet.Cells[3, j]).Text.ToString();
                propertyType=((Excel.Range)m_Worksheet.Cells[4, j]).Text.ToString() ;
                classSource.Append(" private  "+propertyType+"  _" + propertyName + " ;\n");
                classSource.Append(" public   "+propertyType+"   " + "" + propertyName + "\n");
                classSource.Append(" {\n");
                classSource.Append(" get{   return   _" + propertyName + ";}   \n");
                classSource.Append(" set{   _" + propertyName + "   =   value;   }\n");
                classSource.Append(" }\n");
                //classSource.Append("\tpublic " + ((Excel.Range)m_Worksheet.Cells[4, j]).Text.ToString() + " " + ((Excel.Range)m_Worksheet.Cells[3, j]).Text.ToString() + ";\n");
            }
            classSource.Append("}\n");
            //Console.WriteLine(classSource);
            for (int i = 7; i < row_Count + 1; i++)//
            {
                for (int j = 2; j < clomn_Count + 1; j++)
                {
                    objectData.Append(((Excel.Range)m_Worksheet.Cells[i, j]).Text.ToString() + ";");
                    
                }
                objectData.Append("\n");
                try
                {
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
              
                
            }
            //关闭Excel相关对象
            m_Worksheet = null;
            m_Workbook = null;
        }
    }

}

       但是,执行到Excel.Worksheet xlsWs = (Excel.Worksheet)xlsWb.Worksheets[1]时,提示“找不到编译动态表达式所需的一种或多种类型。是否缺少对 Microsoft.CSharp.dll 和 System.Core.dll 的引用? ”错误。

 

       庆幸在③中找到了解决方法,就是在引用“Microsoft.Office.Interop.Excel”的属性中的“嵌入互操作类型”改为“false”就可以了,但是想③中说的,都不知道为什么,我也有不干,虽然问题解决,但是没有彻底弄明白,总是有种不痛快的感觉。

       那就只有继续google,找到参考④⑤⑥的三篇文章,大概有了自己的理解:嵌入互操作类型的目的是为了减轻要将com组件和软件一起部署的负担,只有设为false时编译时会引入com组件程序集才能获得组件中的类型信息。

 

 

        读取Excel数据总算没有问题了,接着就要实现动态创建类,发现⑦中写的比较不错简洁明了,大致明白了原理,改装了一下就有下面代码:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Reflection;
using System.Text.RegularExpressions;

namespace ReadXlsxData
{
    class DynamicClass
    {
        public static Assembly assembly;
        public static string className="DynamicClass";
        //创建编译器实例。   
        public static CSharpCodeProvider provider = new CSharpCodeProvider();
        //设置编译参数。   
        public static CompilerParameters paras = new CompilerParameters();
        //动态创建类
        public static void NewAssembly(string classSource,string className)
        {
            DynamicClass.className = className;
            //Console.Write(classSource);
            paras.GenerateExecutable = false;
            //paras.ReferencedAssemblies.Add("System.dll");
            paras.GenerateInMemory = false;
            paras.OutputAssembly = "MyClass.dll";
            System.Diagnostics.Debug.WriteLine(classSource);
            //编译代码。   
            CompilerResults result = provider.CompileAssemblyFromSource(paras, classSource);

            //获取编译后的程序集。   
            assembly = result.CompiledAssembly;
        }
        //利用数据进行实例化对象,并返回Dictionary进行存储
        public static Dictionary<int,object> NewInstances(string objectData,int keyIndex)
        {
            
            string[] str=objectData.Split('\n');
            string[] strs;
           
            Dictionary<int, object> genObject = new Dictionary<int, object>();
            string strTemp;
            for(int j=0;j<str.Length-1;j++)   
            {
                strTemp = str[j];
                object generatedClass = assembly.CreateInstance(className);
                PropertyInfo[] infos = generatedClass.GetType().GetProperties();
                strs = strTemp.Split(';');
                if (strs.Length-1 == infos.Length)
                {
                    for (int i = 0; i < strs.Length-1; i++)
                    {
                        if (infos[i].PropertyType.Name== "String")
                            
                            infos[i].SetValue(generatedClass, strs[i], null);
                        else 
                        {
                            System.Type t = infos[i].PropertyType; 
                            System.Reflection.MethodInfo method = t.GetMethod("Parse",new Type[]{typeof (string)});
                            //调用方法的一些标志位,这里的含义是Public并且是实例方法,这也是默认的值
                            Object obj = Activator.CreateInstance(t);
                            System.Reflection.BindingFlags flag = System.Reflection.BindingFlags.Public | BindingFlags.Static | System.Reflection.BindingFlags.Instance;
                             //GetValue方法的参数
                            //infos[i].SetValue(generatedClass, null, null);
                            if(strs[i]==""||strs[i]==null)
                            {
                                 infos[i].SetValue(generatedClass, null, null);
                            }
                            
                                
                            else
                            {
                             object[] parameters = new object[] { strs[i] };
            
                            //取得方法返回的值
                             //object returnValue = method.Invoke(obj, flag, Type.DefaultBinder, parameters, null);
                            Console.WriteLine(method.Invoke(obj, flag, Type.DefaultBinder, parameters, null));
                             infos[i].SetValue(generatedClass, method.Invoke(obj, flag, Type.DefaultBinder, parameters, null), null);
                            }
                        }
                    }
                }
                genObject.Add(int.Parse(strs[keyIndex]), generatedClass);
            }
            return genObject;
            
        }

    }
}

       这里还是出现三个问题:

 

              1)在classSource没有添加“[Serializable]\n”后面不能进行序列化,这点没有什么可说的,很简单的。

              2)要把成员变量封装成对应的属性,才能用GetType().GetProperties()返回属性。

              3)在1)添加了“[Serializable]\n”,然后还是在执行“System.Reflection.Assembly assembly = cr.CompiledAssembly;”出现:未能加载文件或程序集“file:///C:Users\Administrator\AppData\Local\Temps\23y9bgt.dll”或它的某一个依赖项。系统找不到指定的文件的错误。

              4)在解决问题3)后,能够进行序列化操作,但是在反序列化出现:未处理SerializationException,无法找到程序集"xxxx,Version=0.0.0,Culture=neutral,PulbicKeyToken=null"。的错误。

 

        对于问题3)和4)有点措手不及,对比了⑦中的差别是他的代码不要求序列化,然后我加了“[Serializable]\n"就有问题了,显然是没有生成对应的dll文件,因为在⑦中CompilerParameters.GenerateInMemory = true;也就是说⑦中生成的dll库没有在本地生成,直接保存在内存中,但是反序列化又要用到dll文件(这是就不能找到)就出现了4)的错误,而问题3)我想是没有“源代码”classSource没有引入基本类库System,所以加上"using System;",就解决了。问题4)只要把编译生成的程序集保存在当前目录下,这样需要的时候(序列化)会自动引入。可以参考⑨有详细一点的说明。

    后面我为代码添加了注释但是注释行尾没有添加“\n”,也出现了3)的问题,所以3)问题的根本原因是自己动态创建的源代码不正确

 

     序列化一般有三种方法:BinaryFormatter,SoapFormatter和XML序列化,各自有不同的差异,下面测试中使用的是BinaryFormatter进行序列化和反序列化的。

     还发现一个细节的差别:Visual Studio 2010 C++调试的当前目录和C#是不一样的,C++是在代码所在的根目录,而C#则是在Debug的目录下。

 

最后给出测试Main函数:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace ReadXlsxData
{
    class MainTest
    {
        public static void Main(string[] args)
        {

            string file = "E:\\work\\ReadXlsxData\\default.xlsx";
            ParseXlsx.ReadExcelFile(file);
            ParseXlsx.CloseExcelApplication();
            DynamicClass.NewAssembly( ParseXlsx.classSource.ToString(),"DynamicClass");
            Dictionary<int,object> dic=DynamicClass.NewInstances(ParseXlsx.objectData.ToString(),0);
            string strFile = "default.data";
            FileStream fs = new FileStream(strFile, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fs, dic);
            Dictionary<int, object> dicT;
            fs.Close();
            fs = new FileStream(strFile, FileMode.Open);
            dicT = (Dictionary<int, object>)formatter.Deserialize(fs);
            Console.Read();  
        }
    }
}

       

        后面把这个工具代码用到Unity的项目中,反序列化时出现两个问题:1)无法找到程序集,2)XXX Field not found in XXX class。其中,问题1)参考11已经给了解决方法,问题2)就很诡异了,因为反序列化用的类是有AS输出的,在中文注释后面换行符使用的是“\n",然后在Unity中总是出现某个类的某个域找不到,但是在代码编辑器中,都不会显示语法错误,然后在VS中对类的代码进行粘贴复制竟然就可以(MonoDevelop还是不可以)了,只是有些注释的换行符被VS转成“\r\n",可能是编译器对中文注释和换行没有解析好导致的,只有输出的时候改成“\r\n”来换行了,好奇葩的Bug呀。

                                                                                                                                                                                                            增补于 2013.11.13

 

       花了一天时间,终于解决了,虽然效率低了点,因为之前没有学过C#,直接就开始用,很多概念还不是很清楚,今天的这几个问题在google上几乎可以检索到(很少)但是没有给出得到解决,然后只有从问题的本质上去理解,加上其他一些相关文章的引导,还是顺利的把问题解决了,而且尝试的技术点也挺多的:读取Excel数据,动态创建类和反射,序列化和反序列化,当然最大的收获是希望能够提升自己对问题的把握度。

    

       转载在文首注明出处:http://dsqiu.iteye.com/admin/blogs/1887702/

 

参考:

selen: http://blog.sina.com.cn/s/blog_6325aebe0100nhmq.html

zhaozhi_1983: http://blog.csdn.net/zhaozhi_1983/article/details/2866099

furenjian: http://www.cnblogs.com/furenjian/articles/3054491.html

pnljs: http://www.cnblogs.com/pnljs/archive/2012/02/20/2359313.html

⑤Daniel Cazzulino's Blog: http://blogs.clariusconsulting.net/kzu/check-your-embed-interop-types-flag-when-doing-visual-studio-extensibility-work/

Sam Ng's Blog: http://blogs.msdn.com/b/samng/archive/2010/01/24/the-pain-of-deploying-primary-interop-assemblies.aspx

永春阁: http://www.cnblogs.com/firstyi/archive/2008/03/07/1094652.html

ACG: http://social.msdn.microsoft.com/forums/en-US/netfxbcl/thread/3b64ce7e-1ad5-4164-b8d0-e3c94970e823

Taotesea: C#动态程序集的加载、创建实例、序列化与反序列化http://blog.sina.com.cn/s/blog_4ba5666e0100vrhb.html

⑨+1 M I developerhttp://www.codeproject.com/Articles/208075/How-to-Read-and-Write-xlsx-Excel-2007-file-Part-I

⑨+2djian: http://www.cnblogs.com/djian/archive/2011/01/25/1944586.html

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 大小: 89 KB
1
1
分享到:
评论
2 楼 DSQiu 2013-06-17  
ray_linn 写道
你可以用OpenSDK 直接读office的格式,Open SDK 是微软在 Office 文件格式成为国际标准之后推出的开发工具。

好的,谢谢,这么有含量的评论
1 楼 ray_linn 2013-06-17  
你可以用OpenSDK 直接读office的格式,Open SDK 是微软在 Office 文件格式成为国际标准之后推出的开发工具。

相关推荐

    Excel序列化代码

    5. **读取和反序列化**:除了序列化,此类还可能包含反序列化功能,即从Excel文件读取数据并恢复为C#对象。 在实际开发中,我们还需要考虑性能优化、错误处理和异常捕获,确保代码的健壮性。例如,使用批处理写入来...

    C# Excel转Json或Js数据格式工具

    3. **转换为Json**:有了数据模型,工具可以利用C#内置的System.Text.Json库或者Newtonsoft.Json(Json.NET)来将数据序列化成Json格式的字符串。这涉及到将对象映射到Json格式的过程。 4. **转换为Js数据格式**:...

    Unity数据 ExcelData 读取包

    3. 序列化与反序列化:包可能提供了将Excel数据转换为C#对象的机制,使得数据可以直接在代码中操作。 4. 运行时数据访问:在游戏运行时,根据需要动态读取和更新Excel数据。 5. 错误处理:处理可能的读取错误,例如...

    c#npoi获取数据有效性序列下拉框的值

    总之,C#与NPOI的组合提供了强大的Excel操作能力,能够方便地读取和处理数据有效性序列下拉框的值。通过理解这些基本操作,你可以构建更复杂的Excel自动化工具,以满足各种业务需求。在进行这样的编程时,确保代码的...

    Protobuf导Excel表C#版

    为了序列化Excel数据,开发者需要遍历Excel表格的行和列,将数据转化为对应的C#对象实例,然后调用`WriteTo`或`SerializeToStream`方法将对象序列化为字节流,并保存为`.data`文件。 反序列化过程则相反,先读取`....

    C#实现excel转json[工具+源码]

    在C#中,我们可以使用`System.Data.OleDb`命名空间的`OleDbConnection`和`OleDbDataAdapter`类来读取Excel数据。首先,建立一个连接字符串,指定Excel文件的位置,然后打开连接,创建`OleDbCommand`对象来执行SQL...

    unity3d 跨平台Excel读取插件

    3. **数据映射与序列化**:数据映射是将Excel表格中的数据转换为Unity3D中的游戏对象或脚本变量的过程。插件可能提供了自动映射功能,通过列标题对应到对象属性,使得数据加载更加自动化。 4. **异步加载**:为了不...

    C#实现 图片转Excel 图片转文本 高精度转换 OCR识别

    这个压缩包中的资源是针对C#开发者设计的一个工具,用于将图片转换为Excel表格和文本,利用了OCR(Optical Character Recognition,光学字符识别)技术。以下是该工具的核心知识点和相关组件的详细解释: 1. **图片...

    C#中串口接收数据并生成Excel和数据图片.zip

    在本文中,我们将深入探讨如何使用C#编程语言进行串口通信,并且讲解如何将接收到的数据处理成Excel表格和数据图片。串口通信是一种在设备间交换数据的常见方式,尤其是在嵌入式系统和物联网(IoT)应用中。C#作为微软...

    .Net C# Json接口读取实例

    序列化是将C#对象转换为JSON字符串的过程,而反序列化则是将JSON字符串转换回C#对象。 对于接口的读取,假设你有一个RESTful API,它返回JSON格式的数据。你可以使用C#的HttpClient类来发送HTTP请求并接收响应,...

    Excel生成Proto类、Proto管理类、Bytes文件 C#源码

    3、通过Proto类,序列化出Bytes文件。 (1)差异化打表,加快打表速度。 (2)强力打表,以上三个流程全部走一遍。 (3) 只更新 Bytes 数据文件 4、自定义Proto读取管理类(可自己实现)。 5、根据Excel文件生成C#...

    C# 可扩展式EXCEL解析工具

    在IT领域,尤其是在数据处理和分析的工作中,Excel表格经常被用作数据存储和组织的主要方式。然而,当需要从Excel文件中提取大量数据或进行自动化处理时,使用编程语言如C#进行解析就显得非常高效。"C#可扩展式EXCEL...

    资源信息导入到本地 包含了读取excel数据把数据保存到本地

    本主题聚焦于如何使用C#编程语言将Excel数据读取并保存到本地,这是一个实用且重要的技能,尤其对于那些需要处理大量结构化数据的开发者来说。下面将详细介绍这个过程,以及涉及到的关键知识点。 首先,我们需要...

    C#(.net) Excel、TXT、xml、http数据流读写交换

    - 当涉及不同格式间的数据交换时,例如从Excel转换为XML,可以先使用相应库读取Excel数据,然后将数据结构化为对象,最后将这些对象序列化为XML。 - 反之,从XML反序列化为对象,再写入Excel或其他格式,可以使用`...

    C#+AE根据点坐标生成shp文件

    TXT文件通常以文本形式存储数据,而Excel文件则提供了一种表格化的结构,便于组织和处理数据。开发人员需要编写代码来解析这些文件,提取出点坐标,这可能涉及到文件I/O操作,字符串处理,以及可能的Excel COM对象...

    c# mvc 导出excel

    而Excel导出则涉及到文件处理和数据序列化。 1. **安装库**: 在C# MVC中,通常会用到第三方库来简化Excel操作。例如,`EPPlus`是一个流行的.NET库,它允许我们创建、读取和修改Excel 2007/2010的Open XML文件。你...

    ASP.NET(C#)导入&导出Excel

    这个主题涵盖了多个知识点,包括文件流处理、数据序列化、Excel对象模型的理解以及如何利用C#语言实现这些操作。下面我们将深入探讨这些核心概念。 1. 文件流处理: 在导入和导出Excel时,文件流是关键。C#中的...

    基于C__NET的Excel表格数据导入数据库技术研究

    这里定义了一些路径变量以及一个用于读取Excel表格数据的类`ReadFormExcel`,同时定义了一个`DataTable`对象用于存储Excel表格数据。 ##### 1.3 使用XML技术 为了处理XML数据,我们需要引入`System.Xml`命名空间,...

    unity 3D读取文件(excel和json)

    总结一下,Unity 3D读取Excel和JSON文件的关键在于选择合适的库或工具,并理解Unity的资源管理和数据序列化机制。对于Excel,可以利用`ExcelDataReader`或`EPPlus`进行解析;对于JSON,Unity内置的`JsonUtility`是...

    Unity读取excel 并存储为json 文件

    在Unity游戏开发中,有时我们需要从Excel文件中获取数据并将其转换为JSON格式,以便在游戏中进行数据管理和交换。本文将详细介绍如何在Unity中实现这一功能。 首先,Unity本身并不直接支持读取Excel文件,因此我们...

Global site tag (gtag.js) - Google Analytics