前言
最近由于项目需求,需要读写操作XML文件,并且存储的XML文件格式会随着导入的数据不同而随时改变(当然导入的数据还是有一定约束的),这样我们要预先定义好XML文件的格式就不太现实了,如何实现不管导入的数据如何变化,我都能正确的把数据解析出来,这就是要实现的动态的XML文件读写操作!如果大家有更好的方式欢迎交流!
具体实现
本文所实现的读写XML文件是使用序列话的方式,具体博文请参考:http://www.cnblogs.com/fish-li/archive/2013/05/05/3061816.html,当然如果只是序列化操作XML文件的话,只需要看这篇博文,也就不需要这篇文章了!
了解了如何序列化操作XML文件了话,你就会知道,要想写入和读取XML文件,我们就需要定义好一个数据类型(这一点很重要),但是问题就出现了,如果我们在编程时就定义好一个数据类型了,当导入的数据改变了,这个数据类型就不适合了,接下来就来解决这个问题
实现一:根据需求创建动态数据类型
废话先不多说,先上代码,代码说话,往往比语言来的直接也更容易懂
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.IO; using System.Diagnostics; namespace XMLDemo { public class ReflectClass { //保存动态生成并编译的类的type对象 Type theType = null; //保存动态生成类的实例 object theClass = null; public Assembly DoOperation() { //未初始化 if (theType == null) { //初始化 return GenerateCode(); } return null; } private Assembly GenerateCode() { //文件名 string fileName = "XmlClass"; ////从编译好的dll文件load一个Assembly //Assembly a = Assembly.LoadFrom(fileName + ".dll"); //从编译好的dll文件load一个Assembly byte[] filedata = File.ReadAllBytes(fileName + ".dll"); Assembly a = Assembly.Load(filedata); //此方法在使用完dll文件后会自动释放资源 return a; } public Assembly DoOperation(List<ExcelData> list) { //未初始化 if (theType == null) { //初始化 return GenerateCode(list); } return null; } private Assembly GenerateCode(List<ExcelData> list) { //文件名 string fileName = "XmlClass"; //打开文件,如果不存在,则创建 Stream s = File.Open(fileName + ".cs", FileMode.Create); //创建一个StreamWriter来写入数据 StreamWriter wrtr = new StreamWriter(s); #region 写入动态创建类的源代码 wrtr.WriteLine("// 动态创建的XmlClass类文件"); //类名,此类是为序列化读取XML文件创建的类文件 string className = "XmlClass"; wrtr.WriteLine("using System;"); wrtr.WriteLine("using System.Xml.Serialization;"); wrtr.WriteLine("public class {0}", className); wrtr.WriteLine("{"); var ch = (from num in list select num.Mark).Distinct().ToList(); foreach (var n in ch) { wrtr.WriteLine(" public " + n + " " + n); wrtr.WriteLine(" {"); wrtr.WriteLine(" get;set;"); wrtr.WriteLine(" }"); } wrtr.WriteLine("}"); foreach (var n in ch) { wrtr.WriteLine("public class {0}", n); wrtr.WriteLine("{"); var nlist = from num in list where (num.Mark == n) select num; foreach (var m in nlist) { wrtr.WriteLine(" public string " + m.Name); wrtr.WriteLine(" {"); wrtr.WriteLine(" get;set;"); wrtr.WriteLine(" }"); } wrtr.WriteLine("}"); } //关闭StreamWriter和文件 wrtr.Close(); s.Close(); #endregion //启动进程编译源文件 //指定参数 ProcessStartInfo psi = new ProcessStartInfo(); File.Delete(fileName + ".dll"); //重新导入数据时删除旧的DLL文件 //启动cmd.exe psi.FileName = "cmd.exe"; //cmd.exe的参数,/c-close,完成后关闭;后为参数,指定cmd.exe使用csc来编译刚才生成的源文件 string compileString = "/c C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\csc.exe /optimize+ /target:library {0}.cs"; psi.Arguments = String.Format(compileString, fileName); //运行时的风格-最小化 psi.WindowStyle = ProcessWindowStyle.Minimized; //启动进程 Process proc = Process.Start(psi); //指定当前在此进程退出前等待 proc.WaitForExit(); //从编译好的dll文件load一个Assembly byte[] filedata = File.ReadAllBytes(fileName + ".dll"); Assembly a = Assembly.Load(filedata); //此方法在使用完dll文件后会自动释放资源 //Assembly a = Assembly.LoadFrom(fileName + ".dll"); //这样使用dll文件会一直被占用,不会释放,造成重新生成前无法自动删除 //删除源文件 //File.Delete(fileName + ".cs"); return a; } } }
以上 ReflectClass类帮助我们实现了通过导入的数据来创建一个供我们使用的编译好的类文件,下面主要讲如果通过这个使用这个编译好的dll文件来写入我们的XML文件
写入动态创建类的源代码那一块真是费了我不少脑细胞,感觉跟写模板类似的!(大家如果根据自己需求改写的话,应该就会体会到)
实现二:通过反射得到动态创建的数据类型并序列化写入XML文件
还是先上代码,XMLHelper帮助类可以去上面的连接去下载,也可以在我提供的Demo里下载,我只根据我的需要添加了一个方法,下面我的一个普通帮助类CommonHelper
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace XMLDemo { public class CommonHelper { /// <summary> /// 写文件 /// </summary> /// <param name="filePath">文件名</param> /// <param name="Context">写的内容</param> /// <returns>是否成功</returns> public static bool FileWriter(string filePath, string Context) { try { //打开文件,如果不存在,则创建 Stream s = File.Open(filePath, FileMode.Create); //创建一个StreamWriter来写入数据 StreamWriter wrtr = new StreamWriter(s); //写入动态创建类的源代码 wrtr.WriteLine(Context); wrtr.Close(); s.Close(); return true; } catch (Exception) { return false; } } /// <summary> /// 读文件 /// </summary> /// <param name="filePath">文件名</param> /// <returns>返回读的内容</returns> public static string FileRead(string filePath) { try { //打开文件,如果不存在,则创建 Stream s1 = File.Open(filePath, FileMode.Open); //创建一个StreamWriter来写入数据 StreamReader reader = new StreamReader(s1); //写入动态创建类的源代码 string xml = reader.ReadToEnd(); reader.Close(); s1.Close(); return xml; } catch (Exception) { return null; } } /// <summary> /// 根据数据创建类,并得到动态创建类的集合 /// </summary> /// <param name="list">导入的数据集合</param> /// <returns>返回类的集合</returns> public static List<object> GetXmlClassInstances(List<ExcelData> list) { List<object> o = new List<object>(); ReflectClass t = new ReflectClass(); var assemblys = t.DoOperation(list); Type[] types = assemblys.GetExportedTypes(); foreach (Type type in types) { o.Add(assemblys.CreateInstance(type.Name)); } return o; } /// <summary> /// 得到动态创建类的集合 /// </summary> /// <returns></returns> public static List<object> GetXmlClassInstances() { List<object> o = new List<object>(); ReflectClass t = new ReflectClass(); var assemblys = t.DoOperation(); Type[] types = assemblys.GetExportedTypes(); foreach (Type type in types) { o.Add(assemblys.CreateInstance(type.Name)); } return o; } } }
主要是一个GetXmlClassInstances(List<ExcelData> list):根据数据创建类,并得到动态创建类的集合
一个是GetXmlClassInstances():得到已经创建好的动态类的集合
下面是一个数据实体类,也就是我们所规范的数据格式
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace XMLDemo { /// <summary> /// 数据实体类 /// </summary> public class ExcelData { /// <summary> /// 根节点 /// </summary> public string Mark { get; set; } /// <summary> /// 子节点 /// </summary> public string Name { get; set; } /// <summary> /// 子节点对应值 /// </summary> public string Value { get; set; } } }
准备工作就绪后就可以进行XML文件的写入了,依然是废话不多说,上代码:
private void btnWrite_Click(object sender, EventArgs e) { List<object> XmlClassInstances = CommonHelper.GetXmlClassInstances(listData); if (InsertXmlClass(XmlClassInstances, listData) == false) { MessageBox.Show("数据导入失败"); return; } } private bool InsertXmlClass(List<object> o, List<ExcelData> list) { for (int i = 1; i < o.Count; i++) { //给类的属性赋值 var nlist = from num in list where (num.Mark == o[i].GetType().Name) select num; foreach (var model in nlist) { PropertyInfo propinfo = o[i].GetType().GetProperty(model.Name); propinfo.SetValue(o[i], model.Value, null); } } var XmlClass = o[0]; //默认第一个类为主体类,主体类的属性为其他类 var tt = XmlClass.GetType(); for (int i = 1; i < o.Count; i++) { //给主体类的属性赋值 PropertyInfo propinfo = tt.GetProperty(o[i].GetType().Name); propinfo.SetValue(XmlClass, o[i], null); } //序列化主体类,得到序列化字符串 string xml = XmlHelper.XmlSerialize(XmlClass, Encoding.UTF8); string FilePath = "QMSPlan.XML"; if (CommonHelper.FileWriter(FilePath, xml) == false) { return false; } return true; }
上面的代码可能有点绕,大家可以单步调试去理解,当时写的时候崩溃了几次,最后才调试好,不知道有什么更好的写法!
实现三:序列化读出XML文件
感觉用语言都表达不出来,还是直接上代码吧
private void btnRead_Click(object sender, EventArgs e) { List<ExcelData> listRead = new List<ExcelData>(); var XmlClass = GetXmlClass(); if (XmlClass == null) { MessageBox.Show("数据读取失败"); return; } //获取所有主体类的属性(即所有的子类集合) PropertyInfo[] ps = XmlClass.GetType().GetProperties(); foreach (var n in ps) { //得到子类 var m = n.GetValue(XmlClass, null); //获取子类的所有属性 PropertyInfo[] x = m.GetType().GetProperties(); foreach (var y in x) { ExcelData model = new ExcelData(); model.Mark = m.GetType().Name; model.Name = y.Name; model.Value = y.GetValue(m, null).ToString(); listRead.Add(model); } } } private object GetXmlClass() { List<object> o = CommonHelper.GetXmlClassInstances(); var XmlClass = o[0]; //默认第一个类为主体类 string FilePath = "QMSPlan.XML"; string xml = CommonHelper.FileRead(FilePath); if (xml == null) { return null; } XmlClass = XmlHelper.XmlDeserialize(XmlClass.GetType(), xml, Encoding.UTF8); return XmlClass; }
上面大量运用了反射的特性,就是因为实现动态读取数据类型而造成的!
结束语
可能大家看完觉得完全没必要这样做,并且大量这样的操作会影响性能,不过这样做实实在在解决了我项目中的难点,实现了根据导入数据的不同,可以实现正确的XML的读写操作,
总结一下:以上总共有三个知识点的应用
1.动态创建数据类型并在程序中使用
2.序列话读写XML文件
3.利用反射特性实现
最后附上demo下载地址:http://files.cnblogs.com/beimeng/XML.demo.rar