分享

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

 阿修罗之狮猿授 2016-05-28

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

       

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

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

        


 Excel的数据有两个作用:

1)动态创建类

上图要创建类为:

C#代码  收藏代码
  1. public class DynamicClass  
  2. {  
  3.        public string id;  
  4.        public string name;  
  5.        public string chapter_x;  
  6.        public unit x;  
  7.        public unit y;  
  8. }  

 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则是把所有对象数据存储起来。

C#代码  收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.IO;  
  6. using System.Reflection;  
  7. using Excel = Microsoft.Office.Interop.Excel;  
  8.   
  9. namespace ReadXlsxData  
  10. {  
  11.     static class ParseXlsx  
  12.     {  
  13.         public static Excel.Application m_ExcelFile = new Excel.Application();  
  14.         public static StringBuilder classSource;  
  15.         public static StringBuilder objectData;  
  16.         public static void CloseExcelApplication()  
  17.         {  
  18.             m_ExcelFile.Quit();  
  19.             m_ExcelFile = null;  
  20.         }  
  21.         public static void ReadExcelFile(string excelFilePath)  
  22.         {  
  23.             classSource = new StringBuilder(); ;  
  24.             objectData = new StringBuilder();  
  25.             Excel._Workbook m_Workbook;  
  26.             Excel._Worksheet m_Worksheet;  
  27.             object missing = System.Reflection.Missing.Value;  
  28.             Console.WriteLine("excelFilePath:" + excelFilePath);  
  29.             m_ExcelFile.Workbooks.Open(excelFilePath, missing, missing, missing, missing, missing, missing, missing, missing, missing  
  30.                 , missing, missing, missing, missing, missing);  
  31.             m_ExcelFile.Visible = false;  
  32.             m_Workbook = m_ExcelFile.Workbooks[1];  
  33.             m_Worksheet = (Excel.Worksheet)m_Workbook.ActiveSheet;  
  34.             int clomn_Count = m_Worksheet.UsedRange.Columns.Count;  
  35.             int row_Count = m_Worksheet.UsedRange.Rows.Count;  
  36.             classSource.Append("using System;\n");  
  37.             classSource.Append("[Serializable]\n");  
  38.             classSource.Append("public   class   DynamicClass \n");  
  39.             classSource.Append("{\n");  
  40.             string propertyName,propertyType;  
  41.             for (int j = 2; j < clomn_Count + 1; j++)  
  42.             {  
  43.                 propertyName = ((Excel.Range)m_Worksheet.Cells[3, j]).Text.ToString();  
  44.                 propertyType=((Excel.Range)m_Worksheet.Cells[4, j]).Text.ToString() ;  
  45.                 classSource.Append(" private  "+propertyType+"  _" + propertyName + " ;\n");  
  46.                 classSource.Append(" public   "+propertyType+"   " + "" + propertyName + "\n");  
  47.                 classSource.Append(" {\n");  
  48.                 classSource.Append(" get{   return   _" + propertyName + ";}   \n");  
  49.                 classSource.Append(" set{   _" + propertyName + "   =   value;   }\n");  
  50.                 classSource.Append(" }\n");  
  51.                 //classSource.Append("\tpublic " + ((Excel.Range)m_Worksheet.Cells[4, j]).Text.ToString() + " " + ((Excel.Range)m_Worksheet.Cells[3, j]).Text.ToString() + ";\n");  
  52.             }  
  53.             classSource.Append("}\n");  
  54.             //Console.WriteLine(classSource);  
  55.             for (int i = 7; i < row_Count + 1; i++)//  
  56.             {  
  57.                 for (int j = 2; j < clomn_Count + 1; j++)  
  58.                 {  
  59.                     objectData.Append(((Excel.Range)m_Worksheet.Cells[i, j]).Text.ToString() + ";");  
  60.                       
  61.                 }  
  62.                 objectData.Append("\n");  
  63.                 try  
  64.                 {  
  65.                 }  
  66.                 catch (Exception ex)  
  67.                 {  
  68.                     Console.WriteLine(ex.Message);  
  69.                 }  
  70.                 
  71.                   
  72.             }  
  73.             //关闭Excel相关对象  
  74.             m_Worksheet = null;  
  75.             m_Workbook = null;  
  76.         }  
  77.     }  
  78.   
  79. }  

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

 

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

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

 

 

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

 

C#代码  收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.CodeDom;  
  6. using System.CodeDom.Compiler;  
  7. using Microsoft.CSharp;  
  8. using System.Reflection;  
  9. using System.Text.RegularExpressions;  
  10.   
  11. namespace ReadXlsxData  
  12. {  
  13.     class DynamicClass  
  14.     {  
  15.         public static Assembly assembly;  
  16.         public static string className="DynamicClass";  
  17.         //创建编译器实例。     
  18.         public static CSharpCodeProvider provider = new CSharpCodeProvider();  
  19.         //设置编译参数。     
  20.         public static CompilerParameters paras = new CompilerParameters();  
  21.         //动态创建类  
  22.         public static void NewAssembly(string classSource,string className)  
  23.         {  
  24.             DynamicClass.className = className;  
  25.             //Console.Write(classSource);  
  26.             paras.GenerateExecutable = false;  
  27.             //paras.ReferencedAssemblies.Add("System.dll");  
  28.             paras.GenerateInMemory = false;  
  29.             paras.OutputAssembly = "MyClass.dll";  
  30.             System.Diagnostics.Debug.WriteLine(classSource);  
  31.             //编译代码。     
  32.             CompilerResults result = provider.CompileAssemblyFromSource(paras, classSource);  
  33.   
  34.             //获取编译后的程序集。     
  35.             assembly = result.CompiledAssembly;  
  36.         }  
  37.         //利用数据进行实例化对象,并返回Dictionary进行存储  
  38.         public static Dictionary<int,object> NewInstances(string objectData,int keyIndex)  
  39.         {  
  40.               
  41.             string[] str=objectData.Split('\n');  
  42.             string[] strs;  
  43.              
  44.             Dictionary<int, object> genObject = new Dictionary<int, object>();  
  45.             string strTemp;  
  46.             for(int j=0;j<str.Length-1;j++)     
  47.             {  
  48.                 strTemp = str[j];  
  49.                 object generatedClass = assembly.CreateInstance(className);  
  50.                 PropertyInfo[] infos = generatedClass.GetType().GetProperties();  
  51.                 strs = strTemp.Split(';');  
  52.                 if (strs.Length-1 == infos.Length)  
  53.                 {  
  54.                     for (int i = 0; i < strs.Length-1; i++)  
  55.                     {  
  56.                         if (infos[i].PropertyType.Name== "String")  
  57.                               
  58.                             infos[i].SetValue(generatedClass, strs[i], null);  
  59.                         else   
  60.                         {  
  61.                             System.Type t = infos[i].PropertyType;   
  62.                             System.Reflection.MethodInfo method = t.GetMethod("Parse",new Type[]{typeof (string)});  
  63.                             //调用方法的一些标志位,这里的含义是Public并且是实例方法,这也是默认的值  
  64.                             Object obj = Activator.CreateInstance(t);  
  65.                             System.Reflection.BindingFlags flag = System.Reflection.BindingFlags.Public | BindingFlags.Static | System.Reflection.BindingFlags.Instance;  
  66.                              //GetValue方法的参数  
  67.                             //infos[i].SetValue(generatedClass, null, null);  
  68.                             if(strs[i]==""||strs[i]==null)  
  69.                             {  
  70.                                  infos[i].SetValue(generatedClass, null, null);  
  71.                             }  
  72.                               
  73.                                   
  74.                             else  
  75.                             {  
  76.                              object[] parameters = new object[] { strs[i] };  
  77.               
  78.                             //取得方法返回的值  
  79.                              //object returnValue = method.Invoke(obj, flag, Type.DefaultBinder, parameters, null);  
  80.                             Console.WriteLine(method.Invoke(obj, flag, Type.DefaultBinder, parameters, null));  
  81.                              infos[i].SetValue(generatedClass, method.Invoke(obj, flag, Type.DefaultBinder, parameters, null), null);  
  82.                             }  
  83.                         }  
  84.                     }  
  85.                 }  
  86.                 genObject.Add(int.Parse(strs[keyIndex]), generatedClass);  
  87.             }  
  88.             return genObject;  
  89.               
  90.         }  
  91.   
  92.     }  
  93. }  

       这里还是出现三个问题:

 

              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函数:

 

C#代码  收藏代码
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Runtime.Serialization.Formatters.Binary;  
  6. using System.IO;  
  7.   
  8. namespace ReadXlsxData  
  9. {  
  10.     class MainTest  
  11.     {  
  12.         public static void Main(string[] args)  
  13.         {  
  14.   
  15.             string file = "E:\\work\\ReadXlsxData\\default.xlsx";  
  16.             ParseXlsx.ReadExcelFile(file);  
  17.             ParseXlsx.CloseExcelApplication();  
  18.             DynamicClass.NewAssembly( ParseXlsx.classSource.ToString(),"DynamicClass");  
  19.             Dictionary<int,object> dic=DynamicClass.NewInstances(ParseXlsx.objectData.ToString(),0);  
  20.             string strFile = "default.data";  
  21.             FileStream fs = new FileStream(strFile, FileMode.Create);  
  22.             BinaryFormatter formatter = new BinaryFormatter();  
  23.             formatter.Serialize(fs, dic);  
  24.             Dictionary<int, object> dicT;  
  25.             fs.Close();  
  26.             fs = new FileStream(strFile, FileMode.Open);  
  27.             dicT = (Dictionary<int, object>)formatter.Deserialize(fs);  
  28.             Console.Read();    
  29.         }  
  30.     }  
  31. }  

       

        后面把这个工具代码用到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./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./kzu/check-your-embed-interop-types-flag-when-doing-visual-studio-extensibility-work/

Sam Ng's Blog: http://blogs./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./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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多