前言
最近由於項目需求,需要讀寫操作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("\tpublic " + n + " " + n); wrtr.WriteLine("\t{"); wrtr.WriteLine("\t\tget;set;"); wrtr.WriteLine("\t}"); } 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("\tpublic string " + m.Name); wrtr.WriteLine("\t{"); wrtr.WriteLine("\t\tget;set;"); wrtr.WriteLine("\t}"); } 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