提高生产性工具(四) - XML数据库的尝试
首先祝大家新年快乐.身体健康,平安就是福气.
对于一般的个人迷你项目,数据量不大的时候,完全没有必要使用数据库,管理数据使用XML就可以了.
自己尝试写了一个XML数据库,插入1w条小记录,大概3M大小,然后将一半数据进行更新,大约耗时3秒钟.
XML数据库其实就是一个内存数据库,数据都在内存里面,速度不慢.
然后由于是XML序列化的,其实ORM也不需要了.每个数据库文件保存一种格式的数据.
废话不说,上代码
先是数据模型:
/* * Created by SharpDevelop. * User: scs * Date: 2014/12/30 * Time: 14:07 * * To change this template use Tools | Options | Coding | Edit Standard Headers. */ using System; using DevKit.Common; namespace DevKit.HtmlUtility { /// <summary> /// Description of Class1. /// </summary> [Serializable] public class CodeSnap { /// <summary> /// 标题 /// </summary> public string Title = string.Empty; /// <summary> /// 描述 /// </summary> public string Descrpition = string.Empty; /// <summary> /// 类别 /// </summary> public string Catalog = string.Empty; /// <summary> /// Tag /// </summary> public string Tag = string.Empty; /// <summary> /// 代码 /// </summary> public string Code = string.Empty; /// <summary> /// 检索 /// </summary> /// <param name="strKeyword">检索关键字</param> /// <returns></returns> Boolean Search(string strKeyword) { return false; } } }
数据模型是领域的数据,但是删除标志,更新时间这些数据库使用的字段也是需要的.由于需要序列化,必须打上[Serializable]
/// <summary> /// 数据库记录 /// </summary> [Serializable] public class Model<T> { /// <summary> /// 删除标志 /// </summary> public Boolean IsDel; /// <summary> /// 统一编号 /// </summary> public string DBId; /// <summary> /// 最后更新时间 /// </summary> public DateTime LastUpdate; /// <summary> /// 数据 /// </summary> public T DataRec; }
最后是数据库引擎的代码,这里用到了深拷贝
/// <summary> /// 数据库引擎 /// </summary> public class XmlDataBase<T> { /// <summary> /// 数据库状态 /// </summary> public string Status = "Close"; /// <summary> /// 数据表 /// </summary> List<Model<T>> list = new List<Model<T>>(); /// <summary> /// 数据库文件 /// </summary> string DBfilename = string.Empty; /// <summary> /// 数据库记录数[Without IsDel] /// </summary> /// <returns></returns> public int getCount() { return list.Count(x => { return !x.IsDel; }); } /// <summary> /// 数据库记录数[With IsDel] /// </summary> /// <returns></returns> public int getCountWithDel() { return list.Count; } /// <summary> /// 新建并且打开数据库 /// </summary> /// <param name="xmlfilename"></param> public XmlDataBase(string xmlfilename) { DBfilename = xmlfilename; if (System.IO.File.Exists(xmlfilename)) { list = Utility.LoadObjFromXml<List<Model<T>>>(DBfilename); } } /// <summary> /// 压缩数据库 /// </summary> public void Compress() { var Compresslist = new List<Model<T>>(); Func<Model<T>,Boolean> inner = (x) => { return (!x.IsDel); }; Compresslist = list.FindAll(new Predicate<Model<T>>(inner)); list = Compresslist; Commit(); } /// <summary> /// 添加 /// </summary> /// <param name="rec"></param> public void AppendRec(T rec) { var dbrec = new Model<T>(); dbrec.DBId = Guid.NewGuid().ToString(); dbrec.DataRec = Common.Utility.DeepCopy(rec); dbrec.LastUpdate = DateTime.Now; list.Add(dbrec); } /// <summary> /// 删除 /// </summary> /// <param name="rec"></param> public void DelRec(Model<T> rec) { rec.IsDel = true; UpdateDB(Utility.DeepCopy(rec)); } /// <summary> /// 更新 /// </summary> /// <param name="rec"></param> public void UpdataRec(Model<T> rec) { UpdateDB(Utility.DeepCopy(rec)); } /// <summary> /// 数据的修改 /// </summary> /// <param name="rec">传递过来对象的深拷贝</param> void UpdateDB(Model<T> rec) { for (int i = 0; i < list.Count; i++) { if (rec.DBId == list[i].DBId) { rec.LastUpdate = DateTime.Now; //不允许内部数据使用外部数据的指针引用 //这里使用深拷贝 list[i] = rec; break; } } } /// <summary> /// 提交更新 /// </summary> public void Commit() { Utility.SaveObjAsXml(DBfilename, list); } /// <summary> /// 检索 /// </summary> /// <param name="SearchMethod"></param> /// <returns>数据对象的深拷贝</returns> public List<Model<T>> Search(Func<T,Boolean> SearchMethod) { Func<Model<T>,Boolean> inner = (x) => { return (SearchMethod(x.DataRec) && !x.IsDel); }; List<Model<T>> t = new List<Model<T>>(); foreach (Model<T> element in list.FindAll(new Predicate<Model<T>>(inner))) { //这里也是将数据的副本给与外部 t.Add(Utility.DeepCopy(element)); } return t; } }
数据库内部有一个列表,列表里面存放着数据记录,每个数据记录包括[业务数据]和[数据库信息]
数据的读取,给外部的是一个数据的深拷贝,这样的话,保证了外部对于数据的修改不会影响内部数据.
在传统的数据库中,一般都是通过TCP协议交换数据的,所以,数据其实也是一个深拷贝.
读取如此,保存数据也是将列表替换为一个外部对象的深拷贝.
每次保存数据的时候,其实是将所有的数据都写入数据XML文件中,当然,数据量少的前提下,这样做是可以的.
下面是一个使用的例子:数据库的New语句
Common.XmlDataBase<CodeSnap> db= new Common.XmlDataBase<CodeSnap>(@"C:\中和软件\CodeSnap.xml");;
void BtnAppendClick(object sender, EventArgs e) { Stopwatch x = new Stopwatch(); x.Start(); for (int i = 0; i < 9999; i++) { var r = new CodeSnap(); r.Title = "Title" + i.ToString(); r.Descrpition = "Descrpition"; r.Tag = "Tag"; r.Code = "Code"; db.AppendRec(r); } db.Commit(); var t = db.Search((m) => { return true; }); for (int i = 0; i < t.Count; i++) { if (i % 2 == 1) { t[i].DataRec.Title = "New Title"; db.UpdataRec(t[i]); } } db.Commit(); x.Stop(); MessageBox.Show(x.Elapsed.ToString()); }
这个只是一个XML数据的雏形,原代码基本上都在这里了.
可以改进的地方大概如下:NameSpace这些XML特有的属性的去除.
<ArrayOfModelOfCodeSnap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
现在Key是用GUID的,这个东西也蛮大的,如果不考虑压缩数据库的问题,可以使用数字连番.
(如果使用数字连番的话,由于压缩数据库会改变数据记录数,可能出现主健重复的问题)
其他压缩,例如时间,现在使用标准的DateTime.Now,所以时间也很冗长.以后可以将时间格式化后,保存为文字列.
<IsDel>false</IsDel> <DBId>ef65bff8-4951-464d-bd8f-432f1148b9f8</DBId> <LastUpdate>2014-12-31T11:02:43.5750566+08:00</LastUpdate>
当然,XML也可以换成JSON的,这样的话,数据可以更小,但是JSON操作还不是NET内置的功能,所以暂时不使用.
里面用到的XML操作和深拷贝代码如下
} /// <summary> /// 保存对象 /// </summary> public static void SaveObjAsXml<T>(string filename, T Obj) { var xml = new XmlSerializer(typeof(T)); var writer = new StreamWriter(filename); xml.Serialize(writer, Obj); writer.Close(); } /// <summary> /// 读取对象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="filename"></param> /// <returns></returns> public static T LoadObjFromXml<T>(string filename) { var xml = new XmlSerializer(typeof(T)); var reader = new StreamReader(filename); T obj = (T)xml.Deserialize(reader); reader.Close(); return obj; } /// <summary> /// 深拷贝 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static T DeepCopy<T>(T obj){ BinaryFormatter bFormatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); bFormatter.Serialize(stream, obj); stream.Seek(0, SeekOrigin.Begin); return (T)bFormatter.Deserialize(stream); }