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)读取效率⾼,在使⽤的就是这个⽅法,但是⽅法1)只能读取单元格的“正⽂”数据,像批注等不能读取到(没有看到相关api)。
我这⾥采⽤的是2)的⽅法且参考②中代码进⾏改装的,把读取Excel的数据保存在StringBuilder classSource,objectData
中,classSource⽤来动态创建类的“源代码”,objectData则是把所有对象数据存储起来。
using System;
using System.Collections.Generic;
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");
trywriteline方法的作用
{
}
catch (Exception ex)
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);
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函数:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。