Unity实用小工具或脚本—反射Sqlite数据库(一)

一、前言

       本文主要介绍在Unity中怎么使用轻量级数据库Sqlite,并利用反射将C#定义的数据类直接映射到数据库的表中,类似于安卓的"DataSupport"类,可以直接按照如下操作保存数据到数据库:

//定义一个类Book
public class Book extends DataSupport {
    ...
}
//添加数据到表中
Book book = new Book();
                book.setName("The Da Vinci Code");
                book.setAuthor("Dan Brown");
                book.setPages(454);
                book.setPrice(16.96);
                book.setPress("Unknow");
                book.save();

而不用在直接操作Sql语句插入数据到数据库,如:

INSERT INTO TABLE_NAME [(column1, column2, column3,...columnN)]  
VALUES (value1, value2, value3,...valueN);

实现数据库的面向对象操作,而不是再在使用过程中直接面对繁琐的Sql语句,避免输入错误带来数据操作错误的问题,而且也非常容易维护。

二、实现

1、配置环境

   我使用的是在Windows下的Unity2018.4.2,配置参考宣雨松-Unity3D研究院之使用C#语言建立本地数据库和Android + Sqlite + Unity3D 踩过的那些坑 & 全流程简介。我也是按照这两篇文章进行配置的,具体过程我也再详细介绍一下。

1.1、首先,在Unity的安装目录中找到Mono.Data.Sqlite.dll 文件 与System.Data.dll文件,如果找不到可以去雨松的文章自行下载,如图所示:

Unity实用小工具或脚本—反射Sqlite数据库(一)_第1张图片

1.2、然后在Unity的Asset目录下建一个Plugins文件夹,然后将刚刚找到的两个Mono.Data.Sqlite.dll 文件 与System.Data.dll文件拖到这个文件夹下,如图所示:

Unity实用小工具或脚本—反射Sqlite数据库(一)_第2张图片

导入之后出现如下错误:这个意思是有导入的dll文件不适合当前Unity设置的.Net版本。

Unity实用小工具或脚本—反射Sqlite数据库(一)_第3张图片

选中System.Data文件,修改它为如图所示:

Unity实用小工具或脚本—反射Sqlite数据库(一)_第4张图片

下载sqlite3文件sqlite3.dll下载地址,选择适合PC平台电脑系统位数的文件,我选择下载的文件如图所示:

Unity实用小工具或脚本—反射Sqlite数据库(一)_第5张图片

并将下载好的文件也托人到unity的Plugin文件下,该文件导入之后不需要进行配置修改。

1.3、最后在PlayerSetting里的设置如图所示:

Unity实用小工具或脚本—反射Sqlite数据库(一)_第6张图片

2、Sql数据库操作代码

2.1、我将雨松的代码进行了一些修改,也建立了一个操作Sql语句命令的工具类,代码如下:主要是创建数据库,后续会加上更加丰富的功能。

using Mono.Data.Sqlite;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace Sql.DbMapping
{
    /// 
    /// 数据库命令执行控制类
    /// 
    public class SqlDbCommandCtr 
    {
        #region 变量

        private SqliteConnection sqlConnect;
        private SqliteCommand sqlCommand;
        private SqliteDataReader sqlReader;

        #endregion

        #region 公有方法

        public SqlDbCommandCtr(string dataBaseUrl,string dbName)
        {
            Open(dataBaseUrl,dbName);
        }

        /// 
        /// 根据路径打开数据库,如果没有就创建数据库
        /// 
        /// 
        public void Open(string dbURL,string dbName)
        {
            if(!File.Exists(dbURL))
            {
                Directory.CreateDirectory(dbURL);
            }
            string tempStr = "URI=file:" + dbURL + "/" + dbName;
            try
            {
                sqlConnect = new SqliteConnection(tempStr);
                sqlConnect.Open();
                Debug.Log("Connected to db");
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
            }
        }
        /// 
        /// 关闭数据库的连接
        /// 
        public void Close()
        {
            if (sqlCommand != null)
            {
                sqlCommand.Dispose();
            }
            sqlCommand = null;
            if (sqlReader != null)
            {
                sqlReader.Dispose();
            }
            sqlReader = null;
            if (sqlConnect != null)
            {
                sqlConnect.Close();
            }
            sqlConnect = null;
            Debug.Log("Disconnected from db.");
        }
        /// 
        /// 执行Sql语句命令,并且返回命令行的读取
        /// 
        /// sql语句命令文本
        /// 
        public SqliteDataReader ExeCommand(string textSqlCmd)
        {
            sqlCommand = sqlConnect.CreateCommand();
            sqlCommand.CommandText = textSqlCmd;
            sqlReader = sqlCommand.ExecuteReader();
            return sqlReader;
        }
        /// 
        /// 创建表,如果表不存在则创建新的,如果存在就不创建
        /// 
        /// 表名称
        /// 表字段列表
        /// 
        public SqliteDataReader CreateTable(string tableName, List listDbField)
        {
            if (null == listDbField || 0 == listDbField.Count)
            {
                throw new SqliteException("创建的表字段为空!");
            }
            string tempCmd = "CREATE TABLE IF NOT exists " + tableName + "(" + listDbField[0].Name + " " + listDbField[0].Type;
            for (int i = 1; i < listDbField.Count; i++)
            {
                tempCmd += ", " + listDbField[i].Name + " " + listDbField[i].Type;
            }
            tempCmd += ")";
            return ExeCommand(tempCmd);
        }
        /// 
        /// 读取整个表
        /// 
        /// 
        /// 
        public SqliteDataReader ReadTable(string tableName)
        {
            string tempCmd = "SELECT * FROM " + tableName;
            return ExeCommand(tempCmd);
        }

        /// 
        /// 插入数据
        /// 
        /// 表名字
        /// 字段列表
        /// 
        public SqliteDataReader InsertData(string tableName, List listDbField)
        {
            if (null == listDbField || 0 == listDbField.Count)
            {
                throw new SqliteException("字段列表内容为空!");
            }
            string tempCmd = "INSERT INTO " + tableName + " ";
            string tempCmdFields = listDbField[0].Name;
            string tempCmdValues = listDbField[0].Value;
            for (int i = 1; i < listDbField.Count; i++)
            {
                tempCmdFields += "," + listDbField[i].Name;
                tempCmdValues += "," + listDbField[i].Value;
            }
            tempCmd += " (" + tempCmdFields + ") " + "VALUES (" + tempCmdValues + ")";
            //  Debug.Log(tempCmd);
            return ExeCommand(tempCmd);
        }
        /// 
        /// 删除满足条件的数据
        /// 
        /// 表名
        /// 条件是或还是且
        /// 条件字段列表,动态参数
        /// 
        public SqliteDataReader DeleteData(string tableName, bool isOr = true, params DB_Field[] listDbField)
        {
            if (null == listDbField || 0 == listDbField.Length)
            {
                throw new SqliteException("字段列表内容为空!");
            }
            string tempCmd = "DELETE FROM " + tableName + " WHERE " + listDbField[0].Name + " = " + listDbField[0].Value;
            string tempIsOr = isOr ? " or " : " and ";
            for (int i = 1; i < listDbField.Length; ++i)
            {
                tempCmd += tempIsOr + listDbField[i].Name + " = " + listDbField[i].Value;
            }

            //Debug.Log(tempCmd);

            return ExeCommand(tempCmd);
        }


        #endregion

    }
}

2.2、将普通类映射到数据库表

这一步是本文的重点操作,定义一个自定义属性映射类AttrMappingClass2DbTable,代码如下:该用来将后续我们自己定义的数据类进行属性标注,将自定数据类的属性或字段类型和数据库进行映射

    /// 
    /// 自定义类到数据库表的属性映射类
    /// 

    [AttributeUsage(AttributeTargets.Property, Inherited = true)]
    public class AttrMappingClass2DbTable : Attribute
    {
        /// 
        /// 类的该成员映射对应数据库表字段的类型。
        /// 
        public string M_FieldType
        {
            get; private set;
        }

        /// 
        /// 类的该成员映射对应数据库表字段的默认值
        /// 
        public object M_DefaultValue
        {
            get; private set;
        }
        /// 
        /// 
        /// 
        /// 类的该成员映射的数据库对应表字段名称。
        /// 类的该成员映射对应数据库表字段的类型。
        /// 类的该成员映射对应数据库表字段的默认值
        public AttrMappingClass2DbTable(string fielType, object defaultValue = null)
        {
            M_FieldType = fielType;
            M_DefaultValue = defaultValue;
        }
    }

定义一个从类到表的映射基类DataClass2DbTable,主要操作为将数据插入、更新到表中等。代码如下:

    #region 类到表的基础类

    /// 
    /// 数据类到表的映射类
    /// 
    public class DataClass2DbTable
    {
        /// 
        /// 映射的表名字
        /// 
        public  string M_TableName { get; private set; }
        /// 
        /// 表中所有的字段数据列表
        /// 
        protected  List listDbAllFields = new List();
        protected SqlDbCommandCtr sqlDbCtr;


        /// 
        /// 创建映射的表
        /// 
        private void CreateMappingTable()
        {
            //如果字段列表已经有数据了,说明已经创建列表了
            if (listDbAllFields.Count > 1) return;
            foreach (PropertyInfo proInfo in this.GetType().GetProperties())
            {
                object[] attrs = proInfo.GetCustomAttributes(typeof(AttrMappingClass2DbTable), true);
                if (0 < attrs.Length)
                {
                    DB_Field tempData = new DB_Field(proInfo.Name);
                    for (int i = 0; i < attrs.Length; i++)
                    {
                        AttrMappingClass2DbTable tempC2TM = (AttrMappingClass2DbTable)attrs[i];
                        tempData.Type = tempC2TM.M_FieldType;
                        tempData.Value = tempC2TM.M_DefaultValue == null ? SqlDataType.NULL : tempC2TM.M_DefaultValue.ToString();
                    }
                    listDbAllFields.Add(tempData);
                }
            }
            //根据当前类的字段列表创建数据库的表
            sqlDbCtr.CreateTable(M_TableName, listDbAllFields);
        }
        /// 
        /// 保存字段列表的值
        /// 
        private void SaveListFieldData()
        {
            Type type = this.GetType();
            for (int i = 0; i < listDbAllFields.Count; i++)
            {
                DB_Field tempDF = listDbAllFields[i];
                PropertyInfo proInfo = type.GetProperty(listDbAllFields[i].Name);
                string tempValue = proInfo.GetValue(this, null).ToString();
                switch (tempDF.Type)
                {
                    //如果是文本类型,则改字段的值需要加‘’号
                    case SqlDataType.TEXT:
                        tempValue = "'" + tempValue + "'";
                        break;
                }
               
                tempDF.Value = tempValue;
                listDbAllFields[i] = tempDF;
            }

        }
        /// 
        ///传入到Sql语句命令要进行的变量过滤,
        ///如sql中text类型的变量“'我是测试'”要加单引号
        /// 
        /// 需要过滤的字段
        /// 
        public static DB_Field ValueFilter(DB_Field field)
        {
            DB_Field tempField = field;
            string tempValue = tempField.Value;
            switch (tempField.Type)
            {
                //如果是文本类型,则改字段的值需要加‘’号
                case SqlDataType.TEXT:
                    tempValue = "'" + tempValue + "'";
                    break;
            }
            tempField.Value = tempValue;
            return tempField;
        }
        public DataClass2DbTable(SqlDbCommandCtr db, string tableName)
        {
            sqlDbCtr = db ?? throw new Exception("数据库为空!");
            M_TableName = tableName;
            CreateMappingTable();
        }
        /// 
        /// 将数据插入到数据库表中
        /// 
        public void Insert()
        {
            //先保存赋值后的数据
            SaveListFieldData();
            //在插入到数据库表中
            sqlDbCtr.InsertData(M_TableName, listDbAllFields);
        }
        public static void Insert(List
listDatas) { } public void Update() { } } #endregion

2.3、整个项目中用到的公共自定义数据类型代码为:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Sql.DbMapping
{

    #region 数据库映射

    /// 
    /// 数据库字段数据
    /// 
    public struct DB_Field
    {
        /// 
        /// 字段的名字
        /// 
        public string Name;
        /// 
        /// 字段的值
        /// 
        public string Value;
        /// 
        /// 字段的类型
        /// 
        public string Type;
        public DB_Field(string name)
        {
            Name = name;
            Value = "";
            Type = SqlDataType.NULL;
        }
    }
    /// 
    /// 字段索引
    /// 
    public struct DB_FieldIndex
    {
        public uint Index;
        public object Value;
    }
    /// 
    /// SQL数据的类型
    /// 
    public sealed class SqlDataType
    {
        #region Sql数据库的基础数据类型
        /// 
        /// 空值类型
        /// 
        public const string NULL = "BULL";
        /// 
        /// 整数类型
        /// 
        public const string INT = "INTEGER";
        /// 
        /// 浮点型数据
        /// 
        public const string REAL = "REAL";
        /// 
        /// 文本类型
        /// 
        public const string TEXT = "TEXT";
        /// 
        /// 二进制类型
        /// 
        public const string BLOB = "BLOB";

        #endregion
        /// 
        /// 主键类型
        /// 
        public const string KEY = "PRIMARY KEY";
        /// 
        /// 自动增加
        /// 
        public const string AUTOADD = "AUTOINCREMENT";
    }
    #endregion

}

2.4、最后定义一个测试的类,继承基类DataClass2DbTable,然后在测试脚本中进行数据库操作,代码如下:

using Sql.DbMapping;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SqlTest : MonoBehaviour
{
    private string dataBaseName = "test3.db";
    private string tableName = "table2";
    private void Start()
    {
        string appDBPath = Application.dataPath + "/Plugins/Android/assets";

        SqlDbCommandCtr db = new SqlDbCommandCtr(appDBPath, dataBaseName);

        TestMappingClass tempMappingCalss = new TestMappingClass(db, tableName);

        tempMappingCalss.M_Id = 0;
        tempMappingCalss.M_Name = "测试";
        tempMappingCalss.M_Score = 78.5f;
        tempMappingCalss.Insert();

        tempMappingCalss.M_Id = 0;
        tempMappingCalss.M_Name = "测试1";
        tempMappingCalss.M_Score = 78.5f;
        tempMappingCalss.Insert();

        TestMappingClass tempMappingCalss2 = new TestMappingClass(db, tableName);

        tempMappingCalss2.M_Id = 1;
        tempMappingCalss2.M_Name = "测试2";
        tempMappingCalss2.M_Score = 89.5f;
        tempMappingCalss2.Insert();

        tempMappingCalss2.M_Id = 1;
        tempMappingCalss2.M_Name = "测试";
        tempMappingCalss2.M_Score = 89.5f;
        tempMappingCalss2.Insert();

        db.Close();

    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
public class TestMappingClass : DataClass2DbTable
{

    [AttrMappingClass2DbTable(SqlDataType.INT)]
    public int M_Id { get; set; }
    [AttrMappingClass2DbTable(SqlDataType.TEXT)]
    public string M_Name { get; set; }
    [AttrMappingClass2DbTable(SqlDataType.REAL)]
    public float M_Score { get; set; }

    public TestMappingClass(SqlDbCommandCtr db, string tableName) : base(db, tableName)
    {

    }
}

可以看见,此处对数据进行插入操作已经非常简单了,只需要顶一个基类DataClass2DbTable派生类的对象,然后对其赋值,最后调动插入数据的方法就可以。打开数据库查看结果如图所示:我这里故意将数据按照雨松写的文章里面安排的放在这个文件夹目录下,其实可以放在任何文件夹,只要修改数据库的路径就可以。

Unity实用小工具或脚本—反射Sqlite数据库(一)_第7张图片

从图中可以看到我们已经将数据插入进去了

三、总结

1、详细介绍了在unity中使用Sqliite数据库的过程和注意事项,最重要的是需要三个dll文件的导入和配置

2、改进了原来在C#里面操作Sql语句和数据进行连接并操作数据库进行增、删、改、查的操作,增加了从类直接到数据库表的数据映射操作,之后可以直接对类进行操作,由类管理和数据库底层Sql语句的代码,大大的简化了数据库操作的开发流程。

3、后续还会对数据库进行更加完善的操作,包括删除、更新和查找等

4、unity工程下载:Unity实用小工具或脚本—以对象形式操作Sqlite数据库(一)

你可能感兴趣的:(Android,Unity,游戏开发)