C#开发训练营

第16课:数据库操作基础——数据表与字段组件

应用开发中,如果需要动态操作数据表,使用通用的模型是一个不错的选择;本节将完成这项工作,主要包括字段信息的管理,以及数据表的基本操作,如创建表、添加字段、清理表、删除表、设置主键等操作。

tFieldType枚举

常用的关系型数据库系统中都支持了大量的数据类型,而这里我们只需要支持一些常用的数据类型,下面的代码(/app_code/data/base/tField.cs)定义了tFieldType枚举,其中定义了本书组件中支持的数据类型。

C#
// 字段类型
public enum tFieldType
{
    Int = 1,
    BigInt = 2,
    Char = 3,
    VarChar = 4,
    Decimal = 5,
    Text = 6,
    DateTime = 7,
    Binary = 8
}

这里支持的数据类型包括:

  • Int,一般指32位有符号整数。
  • BigInt,一般指64位有符号整数。
  • Char,定长字符类型。
  • VarChar,变长字符类型。
  • Decimal,实数,如decimal(6,2)表示整数位和小位数共有6位,其中小数位有2位。
  • Text,大文本类型。
  • DateTime,日期和时间类型。
  • Binary,字节类型。

对于大多数关系型数据库来讲,tFieldType枚举列出的成员都有相应的类型,但有些数据库的数据类型设置比较简单,如SQLite数据库只包含基本的整数、浮点数、文本、二进制等类型,但没有DateTime类型。

使用SQLite数据库时,对于Int和BigInt类型统一使用integer类型;Decimal类型则使用real类型;Char、VarChar和Text类型统一使用Text类型;Binary使用blob类型;对于DateTime类型,可以使用文本类型保存指定格式的日期和时间数据,也可以使用其它的日期和时间处理方案,如使用整数表示日期和时间数,稍后会有详细的讨论。

tField类

tField类用于定义字段相关信息,并通过一系列静态方法创建不同类型的字段对象;tField类的基本定义如下(/app_code/data/base/tField.cs)。

C#
public class tField
{
    private tField() { }
    //
    public string Name { get; set; }
    public tFieldType Type { get; set; }
    public int Length { get; set; }// 文本或实数使用
    public int DecimalPlace { get; set; } // 实数小数位
    public bool Nullable { get; set; }
    public bool Unique { get; set; }
    public string DefaultValue { get; set; }
    // 其它代码...
}

其中,字段信息相关的属性包括:

  • Name属性,定义为string类型,指定字段名。
  • Type属性,定义为tFieldType枚举类型,指定字段的数据类型。
  • Length属性,定义为int类型,指定文本的字符数或实数的总位数。
  • DecimalPlace属性,定义为int类型,指定实数的小数位数。
  • Nullable属性,定义为bool类型,指定字段数据是否允许为null值。
  • Unique属性,定义为bool类型,指定字段数据是否定义唯一约束。
  • DefaultValue属性,定义为string类型,使用字符串形式指定字段的默认值。稍后可以看到,对于数值、文本和日期时间类型的字面值,会根据需要添加单引号。

此外,代码中还定义了一个私有的构造函数,这样在tField类的外部就不能使用new关键字创建tField对象;而创建tField对象的工作会使用一系列的静态方法来实现,首先来看GetInt()和GetBigInt()方法,它们分别创建数据类型为int和bigint的字段对象,如下面的代码。

C#
//Int = 1,
public static tField GetInt(string sName,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.Int,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}
//BigInt = 2,
public static tField GetBigInt(string sName,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.BigInt,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}

GetInt()和GetBigInt()方法使用了相同的参数,分别是:

  • sName,字段名称。
  • nullable,指定字段是否允许为空值(null)。
  • unique,指定字段是否添加唯一约束,表示此字段的数据不能重复。
  • defValue,指定字段的默认值。

定长字符和变长字符类型的定义方式相同,在tField类中分别使用GetChar()和GetVarChar()方法创建字段对象,如下面的代码。

C#
//Char = 3,
public static tField GetChar(string sName, int len,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.Char,
        Length = len,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}
//VarChar = 4,
public static tField GetVarChar(string sName, int len,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.VarChar,
        Length = len,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}

方法中,参数sName指定字段名,参数len指定允许的最大字符数。

创建实数类型的字段对象时使用GetDecimal()方法,定义如下。

C#
//Decimal = 5,
public static tField GetDecimal(string sName, int len, int decPlace,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.Decimal,
        Length = len,
        DecimalPlace = decPlace,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}

方法中,参数sName指定字段名,参数len指定整数部分和小数部分的总位数,参数decPlace指定小数的位数。

最后是大文本、字节类型、日期和时间类型的字段对象创建,如下面的代码。

C#
//Text = 6,
public static tField GetText(string sName,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.Text,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}
//DateTime = 7,
public static tField GetDateTime(string sName,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.DateTime,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}
//Binary = 8
public static tField GetBinary(string sName,
    bool nullable = true, bool unique = false, string defval = "")
{
    return new tField()
    {
        Name = sName,
        Type = tFieldType.Binary,
        Nullable = nullable,
        Unique = unique,
        DefaultValue = defval
    };
}

代码中,GetText()方法用于创建大文本类型字段对象,GetBinary()方法用于创建二进制数据类型字段对象;GetDateTime()方法用于创建日期和时间类型字段对象。

下面,在tTable接口组件中会看到tField类的具体应用。

tTable接口组件

tTable接口及相关组件用于完成对数据表的基本操作,并可以在实际应用中根据需要进行修改和扩展。

首先是tTable接口定义,如下面的代码(/app_code/data/base/tTable.cs)。

C#
using System;
using System.Collections.Generic;

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);
}

tTable接口中定义的成员包括:

  • Jet属性,数据表操作相关的tJet对象。
  • Name属性,数据表名称。
  • Exists()方法,判断数据表是否存在。
  • PrimaryKeys属性,指定主键字段,多个主键时使用逗号分隔。
  • Fields属性,定义为List<tField>对象,指定创建表时一同创建的字段信息。
  • Create()方法,创建数据表,包含ID字段(约定为recid)和Fields属性所包含的字段。
  • Drop()方法,删除数据表。
  • Truncate()方法,重置数据表。表中的ID计数会重新开始,就像新创建的表一样。
  • AddFields()方法,向表中添加字段。

请注意,重置表或删除表都会丢失表中的所有数据,如果觉得这些操作不安全,也可以不使用Truncate()和Drop()方法。

下面的代码(/app_code/data/base/tTable.cs)定义了tTableBase类,作为实现tTable接口类型的基类。

C#
public abstract class tTableBase : tTable
{
    //
    public tTableBase(tJet jet,string sName)
    {
        Jet = jet;
        Name = sName;
    }
    //
    public tJet Jet { get; private set; }
    public string Name { get; private set; }
    //
    public string PrimaryKeys { get; set; }
    public List<tField> Fields { get; set; }
    //
    public abstract bool Exists();
    public abstract bool Create();
    public abstract bool Drop();
    public abstract bool Truncate();
    public abstract bool AddFields(params tField[] fields);
}

tTableBase类中首先定义了构造函数,它需要两个参数,分别指定tJet对象和数据表名称。此外,除了Jet、Name、PrimaryKeys和Fields属性,其它成员都定义为抽象的,需要在tTableBase类的子类中重写。

SQL Server数据库的实现类为tSqlTable,定义为tTableBase类的子类,基本定义如下面的代码(/app_code/data/sqlserver/tSqlTable.cs)。

C#
using System;
using System.Text;

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

tSqlTable类的构造函数通过继承基类构造函数分别指定了Jet和Name属性。接下来讨论其它成员的实现。

首先创建辅助方法GetFieldSql(),其功能是将tField对象的字段信息转换为字段定义语句,tSqlTable类中的实现如下。

C#
// 生成字段定义SQL
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(" nvarchar({0})", fld.Length);
    else if (fld.Type == tFieldType.Char)
        sb.AppendFormat(" nchar({0})", fld.Length);
    else if (fld.Type == tFieldType.DateTime)
        sb.Append(" datetime");
    else if (fld.Type == tFieldType.Binary)
        sb.Append(" varbinary(max)");
    else
        sb.Append(" nvarchar(max)");
    // 是否为空
    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();
}

代码并不难理解,大部分的SQL语法是通用的,需要注意的是SQL Server中的数据类型名称,这里使用了SQL Server 2005之后的新名称,如nchar类型表示Unicode字符集的定长字符类型,nvarcahr表示Unicode字符集的变长字符类型,大文本类型则使用nvarchar(max)定义等。

Exists()方法用于判断数据表是否存在,tSqlTable类中的实现如下。

C#
public override bool Exists()
{
    string sql = "if object_id(@tblname) is null select 0 else select 1;";
    return Jet.GetValue(sql, tPair.Get("tblname", Name)).GetLng() == 1;
}

代码中使用了SQL Server数据库中的object_id()函数判断表是否存在,当数据表存在时,函数返回对象的ID,当数据表不存在时返回null值。语句中使用if语句判断,当object_id()返回null值时数据返回0,否则返回1,以此判断数据表是否存在。生成语句后,会调用tJet.GetValue()方法返回查询结果,如果等于1则Exists()方法返回true,即数据表存在,否则返回false,表示数据表不存在。

Create()方法用于创建数据表,并包含ID字段和Fields属性包含的字段。tSqlTable类中的实现如下。

C#
//
public override bool Create()
{
    string sql = GetCreateSql();
    return Jet.Execute(sql);
}
// 生成表创建SQL
protected string GetCreateSql()
{
    StringBuilder sb = new StringBuilder("create table",256);
    sb.AppendFormat(" [{0}](recid bigint identity(1,1) not null 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(")");
    return sb.ToString();
}

代码中,使用GetCreateSql()方法创建create table语句,然后在Create()方法中执行。SQL Server数据库创建数据表的语句中,ID字段使用identity定义,默认是从1开始,每次增加1,不使用参数和使用identity(1,1)定义的效果是相同的。应用中,也可以指定开始数值和步长,如identity(2,2)就是指定ID从2开始,每次增加2,即ID值都是偶数。

向已存在的数据表中添加字段时使用AddFields()方法,tSqlTable类中的实现如下。

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 ", Name);
    sb.Append(GetFieldSql(fields[0]));
    for (int i = 1; i < fields.Length; i++)
    {
        sb.Append(",");
        sb.Append(GetFieldSql(fields[i]));
    }
    return Jet.Execute(sb.ToString());
}

在SQL Server中向表中添加字段使用alter table语句和add子句,添加的多个字段定义使用逗号分隔,语句格式如下。

SQL Server
alter table <表> add <字段定义1>,<字段定义2>,...

AddFields()方法中,首先创建了alter table语句,然后使用tJet.Execute()方法执行语句;执行成功时AddFields()方法返回true,否则返回false。

重置表和删除的表操作分别使用truncate table和drop table语句,tSqlTable类中的实现如下。

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

truncate table语句和drop table语句都比较简单,代码中直接使用tJet.Execute()方法执行相应的语句,执行成功返回true,否则返回false。

综合测试

下面的代码,我们使用tField和tTable接口组件创建user_main1表。

C#
using System;
using System.Collections.Generic;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string cnnstr = 
            tSqlHelper.GetCnnStr(@".\MSSQLSERVER1", "cdb_demo");
        tJet jet =new tSqlJet(cnnstr);
        //
        List<tField> fld = new List<tField>();
        fld.Add(tField.GetVarChar("username", 30, false, false));
        fld.Add(tField.GetChar("userpwd", 64));
        fld.Add(tField.GetVarChar("email", 30));
        fld.Add(tField.GetInt("locked", false, false, "1"));
        //
        tTable tbl = new tSqlTable(jet, "user_main1");
        tbl.PrimaryKeys = "username";
        tbl.Fields = fld;
        //
        tWeb.WriteLine(tbl.Create());
    }
}

如果页面显示True,则表示代码成功执行。代码的功能是在cdb_demo数据库中创建user_main1表,其中的字段包括:

  • recid,默认的记录ID字段。
  • username,主键,保存用户名信息。
  • userpwd,密码,定义为64字符的定长文本类型。
  • email,保存用户的E-mail地址。
  • locked,用户是否锁定,默认值为1(锁定状态)。

下面的代码会在user_main1表中添加sex字段。

C#
using System;

public partial class Test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string cnnstr = 
            tSqlHelper.GetCnnStr(@".\MSSQLSERVER1", "cdb_demo");
        tJet jet =new tSqlJet(cnnstr);

        //
        tTable tbl = new tSqlTable(jet, "user_main1");
        bool result = tbl.AddFields(tField.GetInt("sex", false, false, "0"));
        tWeb.WriteLine(result);
    }
}

页面显示True时表示成功在user_main1表中添加了sex字段,其定义为int类型,默认值为0。