C#开发训练营

第28课:实现MySQL操作组件(1)

接下来的几课将完成MySQL数据库操作组件的创建工作,这个过程中,可以对数据组件的实现进一步加深映像,为更多的扩展工作提供帮助;也可以对MySQL数据库操作进行回顾。本课实现了支持MySQL数据库的tJet和tTable接口组件。

tJet接口

tJet接口组件是数据库操作的基本组件,包括数据库连接字符串、连接测试、数据库相关特性和SQL执行并返回查询结果等操作;接口定义如下。

C#
public interface tJet
{
    string CnnStr { get; }
    bool Connect();
    //
    string GetObjName(string name);
    string GetParamName(string name);
    char MatchChar { get; }
    string Database { get; }
    //
    bool Execute(string sql, params tPair[] args);
    int ExecuteNonQuery(string sql, params tPair[] args);
    tPair GetValue(string sql, params tPair[] args);
    tPairList GetFirstRow(string sql, params tPair[] args);
    List<object> GetFirstColumn(string sql, params tPair[] args);
    DataTable GetTable(string sql, params tPair[] args);
    //
    List<double> GetData(string sql, params tPair[] args);
}

操作MySQL数据库时,使用tMySqlJet类作为tJet接口中组件实现,下面的代码(/app_code/data/mysql/tMySqlJet.cs)是tMySqlJet类的基本实现,它继承于tJetBase类。

C#
using System;
using System.Collections.Generic;
using System.Data;
using MySql.Data.MySqlClient;

public class tMySqlJet : tJetBase
{
    public tMySqlJet(string sCnnStr) : base(sCnnStr)
    { }
    //
    public override bool Connect()
    {
        try
        {
            using(MySqlConnection cnn = new MySqlConnection(CnnStr))
            {
                cnn.Open();
                return true;
            }
        }
        catch (Exception ex)
        {
            tLog.E(ex, -1000, "tMySqlJet.Connect()");
            return false;
        }
    }
    //
    public override string GetObjName(string name)
    {
        return "`"+name+"`";
    }
    //
    public override string GetParamName(string name)
    {
        return "?" + name;
    }
    //
    public override char MatchChar { get { return '%'; } }
    //
    public override string Database
    {
        get
        {
            MySqlConnectionStringBuilder sb = 
                new MySqlConnectionStringBuilder(CnnStr);
            return sb.Database;
        }
    }
    //
    protected void AddParams(MySqlCommand cmd,params tPair[] args)
    {
        if(args.Length > 0)
        {
            for (int i = 0; i < args.Length; i++)
                cmd.Parameters.AddWithValue(GetParamName(args[i].Name), 
                    args[i].Value);
        }
    }
    // 其它代码...
}

代码中,首先通过继承tJetBase类的构造函数指定数据库连接字符串。接下来实现了tJet接口的一些成员和辅助成员,如:

  • Connect()方法中,使用MySqlConnection对象连接MySQL数据库,正确连接数据时方法返回true,否则返回false。
  • GetObjName()方法返回对象名格式,在MySQL数据库中,对象使用一对重音符号定义(键盘Tab键上方)。
  • GetParamName()方法返回参数名,操作MySQL数据库时,参数名使用?定义,如?username。
  • MatchChar属性定义了模糊查询字符串,这里同样使用%符号。
  • Database属性返回连接的数据库名称,这里使用数据库连接字符串创建MySqlConnectionStringBuilder对象,并通过Database属性获取数据库名称。
  • AddParams()方法,它的功能是将tPair数组指定的参数数据添加到MySqlCommand对象中。需要注意的是,方法中使用了GetParamName()方法将数据名转换为参数名。

下面的代码实现了tJet.Execute()方法,成功执行SQL时返回true,否则返回false。

C#
public override bool Execute(string sql, params tPair[] args)
{
    try
    {
        using(MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd,args);
            IAsyncResult ar = cmd.BeginExecuteNonQuery();
            cmd.EndExecuteNonQuery(ar);
            return true;
        }
    }
    catch(Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.Execute()");
        return false;
    }
}

下面的代码实现了tJet.ExecuteNonQuery()方法,执行SQL并返回影响的记录数量。

C#
public override int ExecuteNonQuery(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            IAsyncResult ar = cmd.BeginExecuteNonQuery();
            return cmd.EndExecuteNonQuery(ar);
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.ExecuteNonQuery()");
        return -1000;
    }
}

执行SQL并获取单个数据时使用GetValue()方法,tMySqlJet类中的实现如下。

C#
public override tPair GetValue(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            return tPair.Get("", cmd.ExecuteScalar());
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.GetValue()");
        return tPair.Get();
    }
}

tJet.GetValue()会返回tPair对象,需要特定类型的数据时,可以使用对应的GetXXX()获取。

获取查询结果的第一行数据时使用GetFirstRow()方法,tMySqlJet类中的实现如下。

C#
public override tPairList GetFirstRow(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            IAsyncResult ar = cmd.BeginExecuteReader();
            using(MySqlDataReader dr = cmd.EndExecuteReader(ar))
            {
                tPairList lst  =new tPairList();
                if (dr.Read())
                {
                    for (int col = 0; col < dr.FieldCount; col++)
                        lst.Add(dr.GetName(col), dr[col]);
                }
                return lst;
            }
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.GetFirstRow()");
        return tPairList.Get();
    }
}

读取查询结果第一列的所有数据时使用GetFirstColumn()方法,实现如下。

C#
public override List<object> GetFirstColumn(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            IAsyncResult ar = cmd.BeginExecuteReader();
            using (MySqlDataReader dr = cmd.EndExecuteReader(ar))
            {
                List<object> lst = new List<object>();
                while (dr.Read()) lst.Add(dr[0]);
                return lst;
            }
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.GetFirstRow()");
        return new List<object>();
    }
}

获取所有查询结果并返回DataTable对象时使用GetTable()方法,实现如下。

C#
public override DataTable GetTable(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            using (MySqlDataAdapter ada = new MySqlDataAdapter(cmd))
            {
                DataSet ds = new DataSet();
                ada.Fill(ds);
                if (ds.Tables[0].Rows.Count > 0) return ds.Tables[0];
                else return null;
            }
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.GetTable()");
        return null;
    }
}

tJet.GetTable()方法中,如果查询结果没有记录会返回null,否则返回包含所有数据行的DataTable对象。

数据分析时,可以从数据库读取一列数据,并通过List<double>对象返回,此时使用GetData()方法,其实现如下。

C#
public override List<double> GetData(string sql, params tPair[] args)
{
    try
    {
        using (MySqlConnection cnn = new MySqlConnection(CnnStr))
        {
            cnn.Open();
            MySqlCommand cmd = cnn.CreateCommand();
            cmd.CommandText = sql;
            AddParams(cmd, args);
            IAsyncResult ar = cmd.BeginExecuteReader();
            using (MySqlDataReader dr = cmd.EndExecuteReader(ar))
            {
                List<double> lst = new List<double>();
                while (dr.Read()) lst.Add(dr.GetDouble(0));
                return lst;
            }
        }
    }
    catch (Exception ex)
    {
        tLog.E(ex, -1000, "tMySqlJet.GetData()");
        return new List<double>();
    }
}

tTable接口

tTable接口组件用于数据表的基本操作,如创建、删除、重置、添加字段等,接口定义如下。

C#
public interface tTable
{
    tJet Jet { get; }
    string Name { get; }
    bool Exists();
    string PrimaryKeys { get; set; }
    List<tField> Fields { get; set; }
    bool Create();
    bool Drop();
    bool Truncate();
    bool AddFields(params tField[] fields);
}

在MySQL中进行数据表操作的tTable接口组件定义为tMySqlTable类,基本定义如下(/app_code/data/mysql/tMySqlTable.cs)。

C#
using System;
using System.Text;

public class tMySqlTable : tTableBase
{
    //
    public tMySqlTable(tJet jet, string sName)
        : base(jet, sName) { }
    // 其它代码...
}

代码中,tMySqlTable定义为tTableBase类的子类,构造函数通过继承指定了Jet和Name属性。

Exists()方法用于判断表是否存在,实现如下。

C#
public override bool Exists()
{
    string sql = @"select TABLE_NAME from information_schema.TABLES 
where TABLE_SCHEMA=?dbname and TABLE_NAME=?tblname";
    return Jet.GetValue(sql,
        tPair.Get("dbname", Jet.Database),
        tPair.Get("tblname", Name)).GetStr() != "";
}

方法中,通过information_schema.TABLES表中的信息判断当前数据库中是否存在指定的数据表;information_schema.TABLES表中的TABLE_SCHEMA字段保存了数据库名称,TABLE_NAME字段保存了数据表名称。

接下来是两个辅助方法,首先是GetFieldSql()方法,其功能是将tField对象中的字段信息转换为字段定义语句;这里需要注意MySQL数据库中的字段类型定义,方法的实现代码如下。

C#
protected string GetFieldSql(tField fld)
{
    StringBuilder sb = new StringBuilder(Jet.GetObjName(fld.Name),32);
    // 字段类型
    if (fld.Type == tFieldType.Int)
        sb.Append(" int");
    else if (fld.Type == tFieldType.BigInt)
        sb.Append(" bigint");
    else if (fld.Type == tFieldType.Decimal)
        sb.AppendFormat(" decimal({0},{1})", fld.Length,fld.DecimalPlace);
    else if (fld.Type == tFieldType.VarChar)
        sb.AppendFormat(" varchar({0})", fld.Length);
    else if (fld.Type == tFieldType.Char)
        sb.AppendFormat(" char({0})", fld.Length);
    else if (fld.Type == tFieldType.DateTime)
        sb.Append(" datetime");
    else if (fld.Type == tFieldType.Binary)
        sb.Append(" longblob");
    else
        sb.Append(" longtext");
    // 是否为空
    if (fld.Nullable == false)
        sb.Append(" not null");
    // 是否唯一约束
    if (fld.Unique == true)
        sb.Append(" unique");
    // 默认值
    if (fld.DefaultValue != null && fld.DefaultValue.Trim() != "")
    {
        if (fld.Type == tFieldType.Int || fld.Type == tFieldType.BigInt ||
            fld.Type == tFieldType.Decimal)
            sb.AppendFormat(" default {0}", fld.DefaultValue);
        else
            sb.AppendFormat(" default '{0}'", fld.DefaultValue);
    }
    //
    return sb.ToString();
}

方法中,首先定义了字段名,这里使用了tJet接口组件的GetObjName()方法将字段名转换为数据库对象格式,如MySQL数据库就是使用一对重音符号(`)定义,而SQL Server数据库中使用方括号定义。

接下来是数据类型,这里使用MySQL数据库中对应的数据类型定义。

当Nullable属性定义为false时,说明字段数据不允许为空,此时,在字段定义中添加not null。

当Unique属性定义为true时,说明字段数据定义为唯一约束,此时,在字段定义中添加unique关键字。

当DefaultValue属性不为空对象(null)和空字符串时定义字段的默认值,此时,当数据类型为数值型直接使用属性内容,如果类型为文本、二进制、日期和时间类型,则使用一对单引号定义默认值内容。

最后,方法会返回字段定义的完整语句。

接下来的GetCreateSql()方法用于生成创建数据表的SQL语句,实现如下。

C#
protected string GetCreateSql()
{
    StringBuilder sb = new StringBuilder("create table",256);
    sb.AppendFormat(" `{0}`(recid bigint not null auto_increment unique",Name);
    // 字段定义
    for (int i = 0; i < Fields.Count; i++)
    {
        sb.Append(",");
        sb.Append(GetFieldSql(Fields[i]));
    }
    // 主键
    if (PrimaryKeys != null)
    {
        string[] pk = PrimaryKeys.Split(new char[] { ',' },
            StringSplitOptions.RemoveEmptyEntries);
        if (pk.Length > 0)
        {
            sb.AppendFormat(",primary key({0}",Jet.GetObjName(pk[0].Trim()));
            for (int i = 1; i < pk.Length; i++)
                sb.AppendFormat(",{0}",Jet.GetObjName(pk[i].Trim()));
            sb.Append(")");
        }
    }
    //
    sb.Append(")engine=innodb default charset='utf8'");
    return sb.ToString();
}

MySQL数据库中,ID字段一般使用bigint类型,并使用auto_increment关键字,此时,字段数据会从1开始,每次加1;这里,同时在ID字段中定义了唯一约束(unique)。当Fields属性不为空引用时,通过一个for循环结构将字段定义语句添加到表的创建语句中;在字段下定义后,还使用primary key子句定义了表的主键。最后,将数据表引擎定义为innodb,字符集定义为utf8。

Create()方法用于创建表,创建语句会通过tJet.Execute()方法执行,创建成功时方法返回true,否则返回false,实现代码如下。

C#
public override bool Create()
{
    string sql = GetCreateSql();
    return Jet.Execute(sql);
}

需要向已存在的表添加字段时,可以使用AddFields()方法,实现代码如下。

C#
public override bool AddFields(params tField[] fields)
{
    if (fields.Length == 0) return false;
    StringBuilder sb = new StringBuilder(256);
    sb.AppendFormat("alter table `{0}` add column", Name);
    sb.Append(GetFieldSql(fields[0]));
    for (int i = 1; i < fields.Length; i++)
    {
        sb.Append(",add column ");
        sb.Append(GetFieldSql(fields[i]));
    }
    return Jet.Execute(sb.ToString());
}

在MySQL数据库中,向表中添加字段使用alter table语句和add column子句;这里需要注意,添加多个字段时需要使用多个add column子句。

重置表和删除表的操作比较简单,分别使用truncate table和drop table语句,实现代码如下。

C#
//
public override bool Drop()
{
    return Jet.Execute("drop table `" + Name + "`");
}
//
public override bool Truncate()
{
    return Jet.Execute("truncate table `" + Name + "`");
}