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 developer: http://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
相关推荐
5. **读取和反序列化**:除了序列化,此类还可能包含反序列化功能,即从Excel文件读取数据并恢复为C#对象。 在实际开发中,我们还需要考虑性能优化、错误处理和异常捕获,确保代码的健壮性。例如,使用批处理写入来...
3. **转换为Json**:有了数据模型,工具可以利用C#内置的System.Text.Json库或者Newtonsoft.Json(Json.NET)来将数据序列化成Json格式的字符串。这涉及到将对象映射到Json格式的过程。 4. **转换为Js数据格式**:...
3. 序列化与反序列化:包可能提供了将Excel数据转换为C#对象的机制,使得数据可以直接在代码中操作。 4. 运行时数据访问:在游戏运行时,根据需要动态读取和更新Excel数据。 5. 错误处理:处理可能的读取错误,例如...
总之,C#与NPOI的组合提供了强大的Excel操作能力,能够方便地读取和处理数据有效性序列下拉框的值。通过理解这些基本操作,你可以构建更复杂的Excel自动化工具,以满足各种业务需求。在进行这样的编程时,确保代码的...
为了序列化Excel数据,开发者需要遍历Excel表格的行和列,将数据转化为对应的C#对象实例,然后调用`WriteTo`或`SerializeToStream`方法将对象序列化为字节流,并保存为`.data`文件。 反序列化过程则相反,先读取`....
在C#中,我们可以使用`System.Data.OleDb`命名空间的`OleDbConnection`和`OleDbDataAdapter`类来读取Excel数据。首先,建立一个连接字符串,指定Excel文件的位置,然后打开连接,创建`OleDbCommand`对象来执行SQL...
3. **数据映射与序列化**:数据映射是将Excel表格中的数据转换为Unity3D中的游戏对象或脚本变量的过程。插件可能提供了自动映射功能,通过列标题对应到对象属性,使得数据加载更加自动化。 4. **异步加载**:为了不...
这个压缩包中的资源是针对C#开发者设计的一个工具,用于将图片转换为Excel表格和文本,利用了OCR(Optical Character Recognition,光学字符识别)技术。以下是该工具的核心知识点和相关组件的详细解释: 1. **图片...
在本文中,我们将深入探讨如何使用C#编程语言进行串口通信,并且讲解如何将接收到的数据处理成Excel表格和数据图片。串口通信是一种在设备间交换数据的常见方式,尤其是在嵌入式系统和物联网(IoT)应用中。C#作为微软...
序列化是将C#对象转换为JSON字符串的过程,而反序列化则是将JSON字符串转换回C#对象。 对于接口的读取,假设你有一个RESTful API,它返回JSON格式的数据。你可以使用C#的HttpClient类来发送HTTP请求并接收响应,...
3、通过Proto类,序列化出Bytes文件。 (1)差异化打表,加快打表速度。 (2)强力打表,以上三个流程全部走一遍。 (3) 只更新 Bytes 数据文件 4、自定义Proto读取管理类(可自己实现)。 5、根据Excel文件生成C#...
在IT领域,尤其是在数据处理和分析的工作中,Excel表格经常被用作数据存储和组织的主要方式。然而,当需要从Excel文件中提取大量数据或进行自动化处理时,使用编程语言如C#进行解析就显得非常高效。"C#可扩展式EXCEL...
本主题聚焦于如何使用C#编程语言将Excel数据读取并保存到本地,这是一个实用且重要的技能,尤其对于那些需要处理大量结构化数据的开发者来说。下面将详细介绍这个过程,以及涉及到的关键知识点。 首先,我们需要...
- 当涉及不同格式间的数据交换时,例如从Excel转换为XML,可以先使用相应库读取Excel数据,然后将数据结构化为对象,最后将这些对象序列化为XML。 - 反之,从XML反序列化为对象,再写入Excel或其他格式,可以使用`...
TXT文件通常以文本形式存储数据,而Excel文件则提供了一种表格化的结构,便于组织和处理数据。开发人员需要编写代码来解析这些文件,提取出点坐标,这可能涉及到文件I/O操作,字符串处理,以及可能的Excel COM对象...
而Excel导出则涉及到文件处理和数据序列化。 1. **安装库**: 在C# MVC中,通常会用到第三方库来简化Excel操作。例如,`EPPlus`是一个流行的.NET库,它允许我们创建、读取和修改Excel 2007/2010的Open XML文件。你...
这个主题涵盖了多个知识点,包括文件流处理、数据序列化、Excel对象模型的理解以及如何利用C#语言实现这些操作。下面我们将深入探讨这些核心概念。 1. 文件流处理: 在导入和导出Excel时,文件流是关键。C#中的...
这里定义了一些路径变量以及一个用于读取Excel表格数据的类`ReadFormExcel`,同时定义了一个`DataTable`对象用于存储Excel表格数据。 ##### 1.3 使用XML技术 为了处理XML数据,我们需要引入`System.Xml`命名空间,...
总结一下,Unity 3D读取Excel和JSON文件的关键在于选择合适的库或工具,并理解Unity的资源管理和数据序列化机制。对于Excel,可以利用`ExcelDataReader`或`EPPlus`进行解析;对于JSON,Unity内置的`JsonUtility`是...
在Unity游戏开发中,有时我们需要从Excel文件中获取数据并将其转换为JSON格式,以便在游戏中进行数据管理和交换。本文将详细介绍如何在Unity中实现这一功能。 首先,Unity本身并不直接支持读取Excel文件,因此我们...