`

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生成Proto类、Proto管理类、Bytes文件 C#源码

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

    C#基础类库

    序列化帮助类,Xml序列化,Json序列化,SoapFormatter序列化,BinaryFormatter序列化 27.压缩解压缩 SharpZip 28.验证码 YZMHelper Captcha 验证码类,一个很个性的验证码类 29.页面辅助类 HTMLHelper ...

    C#用户权限控制架构,可以控制到类

    xml序列化与反序列化 权限控制原理 Lm.Common.Auth 帐号、角色、权限管理 Lm.Common.Auth.Database 权限管理相关的数据库结构 Lm.Common.Auth.Doc 权限管理的流程图 Lm.Common.Auth.Examples 权限管理...

    C#通讯录代码 2.0

    1、类的序列化 2、从注册表中存储和读取二进制信息 3、用GDI 画出简单的图 4、DataGridView的使用,包括显示序号、显示不同背景色 5、TreeView使用,包括多级树的创建、树节点的拖动、3鼠标右键选中树节点 6、控件...

    C#基类库大全下载--苏飞版

    通信相关 1.C#HttpHelper,Httpwebrequest,... 序列化帮助类,Xml序列化,Json序列化,SoapFormatter序列化,BinaryFormatter序列化 27.压缩解压缩 SharpZip 28.验证码 YZMHelper Captcha 验证码类,一个很个性的验证码类

    C#开发经验技巧宝典

    0780 如何序列化对象 473 0781 如何实现特殊形状的窗体 474 0782 如何移动正在使用的文件 474 0783 如何实现类似QQ的程序界面 475 0784 如何实现动画显示窗体 476 0785 如何显示和隐藏任务栏 477 0786 ...

    C#编程经验技巧宝典

    107 &lt;br&gt;0176 如何动态改变数组长度 108 &lt;br&gt;0177 如何反转数组中元素的顺序 108 &lt;br&gt;0178 如何排序数组中的元素的顺序 109 &lt;br&gt;0179 如何创建动态数组 110 &lt;br&gt;5.5 NET应用技巧 110 ...

    ASP升级.net资料大全(c#入门 语言规范 源码教程 学习笔记 技术资料 面试题 asp与.net代码生成器)

    分页及动态创建列的控件 .txt 改变Windows服务的启动顺序.txt 各进制之间的转换.txt 给图片添加版权信息.txt 关于拖放操作.txt 关于在WinForm里用HttpWebRequest获得某个页面,并填写页面的textbox及点击...

    C#基类库(苏飞版)

    序列化帮助类,Xml序列化,Json序列化,SoapFormatter序列化,BinaryFormatter序列化 27.压缩解压缩 SharpZip 28.验证码 YZMHelper Captcha 验证码类,一个很个性的验证码类 29.页面辅助类 HTMLHelper ...

    asp.net知识库

    泛型的序列化问题 .NET 2.0 泛型在实际开发中的一次小应用 C#2.0 Singleton 的实现 .Net Framwork 强类型设计实践 通过反射调用類的方法,屬性,字段,索引器(2種方法) ASP.NET: State Server Gems 完整的动态加载/卸载...

    .net技术资料大全(语言规范 源码教程 学习笔记 技术资料 .net代码生成器)

    分页及动态创建列的控件 .txt 改变Windows服务的启动顺序.txt 各进制之间的转换.txt 给图片添加版权信息.txt 关于拖放操作.txt 关于在WinForm里用HttpWebRequest获得某个页面,并填写页面的textbox及点击...

    ctl-data:.NET库为CSV,固定宽度和类似格式提供了健壮,功能丰富的高性能解析器

    ctl数据 Ctl.Data为CSV(包括其以制表符分隔,管道分隔等的变体),固定宽度和XLSX文件提供解析器。 尽管我们更喜欢诸如Web服务之类... 序列化和验证跳过了缓慢的反思,并使用代码生成来确保这些便利不会使事情变慢。

Global site tag (gtag.js) - Google Analytics