XML万能数据库设计
XML万能数据库设计
使用unity开发存取本地数据一般用xml,来实现跨平台的数据存取。为什么不用sqlite我就不解释了,谁用谁知道。
好进入正题,如果你了解hibernate,应该知道他是针对model层数据持久化操作的利器。什么意思呢,也就是说任意对象的增删改查它都帮你做了,你需要做的就是配置一下即可。使用时直接调用提供的接口。Java,C#都有这样的利器。
但是hibernate一般用于web应用,需要处理大量的实体类,虽然用unity开发的游戏也需要对一些类进行持久化操作,但是用hibernate还是太专业了,大材小用不说,好像并不怎么适用。配置什么的应该很烦。但是还想一劳永逸,只写一个操作适用所有对象的增删改查怎么办?自己动手丰衣足食。
首先要知道hibernate是怎么实现的类的自动拆装的,反射。OK,接下来就简单了,知道了方案,接下来就是具体的实现了。楼主看了下C#的反射,没想到实在是太好用了!
好,整理一下基本思路,类的名称作为表名,类的成员变量作为列名,成员变量的值作为值,进行存取。等等,xml又不是sql,这表,列,值代表什么意思呢?如果你学过xml的话,应该知道根,元素。(小白先去学基础知识吧),所以我们存取对象就是把这个对象的名称作为根,字段作为子根进行存取。
以上是xml实现基础,如何做到万能,还需要结合反射,通过反射可以获取的信息:类的名称,类的成员变量,值,等等。所以,存取对象的时候,先将这个类进行解析,获取类名,他的成员变量,以及值,存起来,然后插入的时候,以类名为根,成员变量作为元素,值作为元素的值插入。因为xml存的是字符,所以类型在存的时候都转成字符串。取的时候再将字符串转成相应的类型,封装成对象即可。
不知道听不听的懂,代码是最通俗的,好,进入正题。
首先,你要处理的是泛型对象,这样在操作的时候不需要强转。先写个接口声明一下。插入的时候直接传对象,查找的时候也传对象(默认按ID查找,需要在代码中对ID赋值),更新有两种,一是更新指定的列,二是更新对象的所有列。删除直接传对象。
using System.Collections.Generic; using System; public interface DataBase { void Insert<T>(T t); T Select<T>(T t); List<T> SelectAll<T>(T t); void Update<T>(T t,string key); void Update<T>(T t); void Delete<T>(T t); void CreateData(string path); }
然后,写个静态的类,使用这个万能xml,代码也很简单,如果有疑惑,我稍后解答,例如为什么写成静态的?
using System.Collections.Generic; using System; using UnityEngine; public class MyDataBase { private static DataBase database; public static string RESDATAPATH = Application.streamingAssetsPath; //源数据目录 private static string DATAPATH = Application.dataPath; //数据目录 static MyDataBase() { #if UNITY_ANDROID DATAPATH = Application.persistentDataPath; #endif database = new XmlDataBase(DATAPATH); } public static void Insert<T>(T t) { database.Insert(t); } public static T Select<T>(T t) { return database.Select(t); } public static List<T> SelectAll<T>(T t) { return database.SelectAll<T>(t); } public static void Update<T>(T t, string key) { database.Update(t, key); } public static void Update<T>(T t) { database.Update(t); } public static void Delete<T>(T t) { database.Delete(t); } }
好,下面就是xml的存取操作了,不怎么难,关键是一些方法的运用。
using UnityEngine; using System.Xml; using System.Collections.Generic; using System.IO; using System; public class XmlDataBase : DataBase { private string path = "/DataBase/GameData.xml"; private string Myroot = "MyData"; private XmlDocument xmlDoc = new XmlDocument(); public static string ObjectID="ID"; public static string PATH; private void ReadFile(string path) { PATH = path + this.path; if (!File.Exists(PATH))//如果指定的路径不存在 { if (!File.Exists(MyDataBase.RESDATAPATH + this.path))//如果不存在源文件 { Directory.CreateDirectory(MyDataBase.RESDATAPATH + "/DataBase"); File.CreateText(PATH); CreateData((MyDataBase.RESDATAPATH + this.path)); Application.Quit(); } else { Directory.CreateDirectory(path + "/DataBase"); File.CreateText(PATH); File.Copy(MyDataBase.RESDATAPATH + this.path, PATH); File.Delete(MyDataBase.RESDATAPATH + this.path); } } else { xmlDoc.Load(PATH); } } public XmlDataBase() { //这是一个XML数据库,基于XML的对象的存取,改查操作。 //注意,数据对象的ID需要以字母开头,且不能有特殊字符。 //TextAsset textAsset = (TextAsset)Resources.Load(file, typeof(TextAsset)); ReadFile(path);//读取默认路径 } public XmlDataBase(string path) { ReadFile(path);//指定读取默认路径 } public void CreateData(string path) { XmlDocument xmlDoc = new XmlDocument(); XmlDeclaration xmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null); XmlNode root = xmlDoc.CreateElement(Myroot); xmlDoc.AppendChild(xmlDeclaration); xmlDoc.AppendChild(root); xmlDoc.Save(path); } public void Insert<T>(T obj) { //插入一个对象 //对象有字段,属性值 //利用数据的特点,即名称不同,优化查询速度,具体的做法为,将属性值作为子根操作。 ObjectPara<T> OP = new ObjectPara<T>(obj); // XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 if (Myroots == null) { Myroots = xmlDoc.CreateElement(OP.ObjName); root.AppendChild(Myroots); } //以对象表为根,创建一个以ID为根的子根 XmlNode node = Myroots.SelectSingleNode(OP.GetKeyValue(ObjectID).ToString()); if (node != null) {//如果待插入的对象已经存在,则将此数据删除后,再重新插入 Delete<T>(obj); Insert<T>(obj); return; } XmlElement xe1 = xmlDoc.CreateElement(OP.GetKeyValue(ObjectID).ToString());//创建一个<data>节点 for (int i = 0; i < OP.cols.Length; i++) { XmlElement xe = xmlDoc.CreateElement(OP.cols[i]); xe.InnerText = OP.values[i]; xe1.AppendChild(xe); } if (Myroots == null) { Myroots = xmlDoc.CreateElement(OP.ObjName); root.AppendChild(Myroots); } else { Myroots.AppendChild(xe1); } //添加到<bookstore>节点中 xmlDoc.Save(PATH); OP = null; } public List<T> SelectAll<T>(T obj) { List<T> list = new List<T>(); ObjectPara<T> OP = new ObjectPara<T>(obj); XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 XmlNodeList nodes = Myroots.ChildNodes;//获取对象的所有子对象 foreach (XmlNode node in nodes)// { XmlNodeList lis = node.ChildNodes;//获取每个对象的字段 T o = obj; int count = lis.Count; string[] values = new string[count]; for (int i = 0; i < lis.Count; i++) { values[i] = lis.Item(i).InnerText.Trim(); } o = OP.GetObject(values); list.Add(o); } return list; } public T Select<T>(T obj) { ObjectPara<T> OP = new ObjectPara<T>(obj); XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 XmlNode node = Myroots.SelectSingleNode(OP.GetKeyValue(ObjectID).ToString()); //获取指定列为根的根元素 if (node != null) { XmlNodeList list = node.ChildNodes;//获取根的所有字段 int count = list.Count; string[] values = new string[count]; for (int i = 0; i < list.Count; i++) {//成员变量 //将遍历出的值赋予数组 values[i] = list.Item(i).InnerText.Trim(); } obj = OP.GetObject(values); } else { obj = default(T); MyTool.P(213); } return obj; } public void Update<T>(T obj) { ObjectPara<T> OP = new ObjectPara<T>(obj); // XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 XmlNode node = Myroots.SelectSingleNode(OP.GetKeyValue(ObjectID).ToString()); if (node != null) { XmlNodeList xnl = node.ChildNodes; for (int i = 0; i < xnl.Count;i++ ) {//成员变量 //将遍历出的值赋予数组 xnl.Item(i).InnerText = OP.values[i]; } xmlDoc.Save(PATH); } } public void Update<T>(T obj, string key) { //插入一个对象 //对象有字段,属性值 //利用数据的特点,即名称不同,优化查询速度,具体的做法为,将属性值作为子根操作。 ObjectPara<T> OP = new ObjectPara<T>(obj); // XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 XmlNode node = Myroots.SelectSingleNode(OP.GetKeyValue(ObjectID).ToString()); if (node != null) { //找到名为name的对象 XmlNode xl = node.SelectSingleNode(key);//找到字段 xl.InnerText = OP.GetKeyValue(key).ToString(); xmlDoc.Save(PATH); } } public void Delete<T>(T obj) { ObjectPara<T> OP = new ObjectPara<T>(obj); XmlNode root = xmlDoc.SelectSingleNode(Myroot);//查找<game data> XmlNode Myroots = root.SelectSingleNode(OP.ObjName);//查询对象表 XmlNode node = Myroots.SelectSingleNode(OP.GetKeyValue(ObjectID).ToString()); if (node != null) { node.RemoveAll(); Myroots.RemoveChild(node); xmlDoc.Save(PATH); } } }
因为xml的结构我们要动态生成的,不需要专门针对某一个类写操作语句。如何动态就靠解析类完成了。
好,下面是这个解析类,也就是用反射,本身并不难,关键是如何运用。特别声明的是,因为我操作的数据包含数组,而且数组只用到了字符数组,整型数组,以及浮点数组,所以只写了这三个数组的解析。其他类型数组你看需求自己定义:
using System; using System.Reflection; using UnityEngine; using System.Collections.Generic; using System.Runtime.InteropServices; public class ObjectPara<T> { public string ObjName;//待解析的类的名称 public string[] cols;//类的成员变量 public string[] values;//成员变量对应的值 public PropertyInfo[] info;//反射获取类的属性 public Type objtype;//待解析类型 public T obj;//泛型对象 public T GetObject(string[] values) { T _obj = obj; for (int i = 0; i < info.Length; i++) { if (objtype.GetProperty(cols[i]).PropertyType.IsArray) { Type type = objtype.GetProperty(cols[i]).PropertyType; string[] vale = values[i].Split(new char[] { ',' }); if (type == typeof(string[])) { info[i].SetValue(_obj, vale, null); } else if (type == typeof(int[])) { int[] va = new int[vale.Length]; for (int j = 0; j < va.Length; j++) { va[j] = int.Parse(vale[j]); } info[i].SetValue(_obj, va, null); } else if (type == typeof(float[])) { float[] va = new float[vale.Length]; for (int j = 0; j < va.Length; j++) { va[j] = float.Parse(vale[j]); } info[i].SetValue(_obj, va, null); } //MyTool.P(values[i]); } else { info[i].SetValue(_obj, Convert.ChangeType(values[i], info[i].PropertyType), null); } } return _obj; } public ObjectPara() { } public ObjectPara(T obj) { this.obj = obj; AnalyzeObject(); } public void AnalyzeObject()//解析对象 { //获取对象的名称,字段,以及值,以字符串数组的形式存储 objtype = obj.GetType(); ObjName = objtype.Name; info = objtype.GetProperties(); cols = new string[info.Length]; values = new string[info.Length]; for (int i = 0; i < info.Length; i++) { cols[i] = info[i].Name; string value = ""; if (objtype.GetProperty(cols[i]).PropertyType.IsArray) { if (objtype.GetProperty(cols[i]).GetValue(obj, null) != null) { Type type = objtype.GetProperty(cols[i]).PropertyType; if (type == typeof(string[])) { MyTool.P(value); string[] ob = objtype.GetProperty(cols[i]).GetValue(obj, null) as string[]; if (ob != null) { foreach (var o in ob) { value += o + ","; } //value = value.Substring(0, value.Length-2); value = value.TrimEnd(new char[] { ',' }); } } else if (type == typeof(int[])) { int[] ob = objtype.GetProperty(cols[i]).GetValue(obj, null) as int[]; if (ob != null) { foreach (var o in ob) { value += o + ","; } //value = value.Substring(0, value.Length-2); value = value.TrimEnd(new char[] { ',' }); } } else if (type == typeof(float[])) { float[] ob = objtype.GetProperty(cols[i]).GetValue(obj, null) as float[]; if (ob != null) { foreach (var o in ob) { value += o + ","; } //value = value.Substring(0, value.Length-2); value = value.TrimEnd(new char[] { ',' }); } } } } else { value = Convert.ToString(objtype.GetProperty(cols[i]).GetValue(obj, null)); } //MyTool.P(value); values[i] = value; } } public System.Object GetKeyValue(string col) { return (objtype.GetProperty(col).GetValue(obj, null)); } }
好了,以上就是设计万能xml的部分,如何使用呢?
很简单,你先写一个类,这个类很简单,或者说为了简单,只有一个成员变量。这样可以统一对所有对象进行操作,
public class GameData { private string iD; public string ID { get { return iD; } set { iD = value; } } }
然后随便写一个实体类,例如Person
public class MyPerson :GameData{ private string name; public string Name { get { return name; } set { name = value; } } private int sort; public int Sort { get { return sort; } set { sort = value; } } private float life; public float Life { get { return life; } set { life = value; } } }
接下来就是一个简单的测试类了,我就不传代码了。各位自己测试:
MyPerson p = new Myperson();
p.ID="lihua";
……
MyDataBase.Insert(p);添加
MyDataBase.Delete(p);删除
……
注意一下的问题,不要以为复制完代码就可以运行,主要是目录路径问题,如果你实在搞不定,直接先在Assent目录下建立一个路径DataBase,然后再建一个GameData.xml。好,接下来看你发挥。
后记:路径弄得有问题,建议不要使用上面提到的检测数据文件的方法,可以在进入游戏时检测并初始化数据库。相关的代码也很简单,主要是使用平台判断,www类,以及协程的使用。
using UnityEngine; using System.Collections; using System.IO; public class CheckData : MonoBehaviour { public string resData; public string gameData; public string path = "/GameData.xml"; // Use this for initialization void Awake() { resData = Application.streamingAssetsPath + path; #if UNITY_ANDROID gameData = Application.persistentDataPath+path; #else gameData = Application.dataPath + path; #endif StartCoroutine(ReadData(gameData)); } IEnumerator ReadData(string path) { if (!File.Exists(path)) { #if UNITY_ANDROID WWW www = new WWW(resData); yield return www; if(www.isDone) { File.WriteAllBytes(path,www.bytes); } #else File.Copy(resData, path); #endif Application.LoadLevel(1); } else { Application.LoadLevel(1); } yield return 0; } }