内容


利用 ADO.NET 连接到 Informix

从 Connection 到强类型 DataSet

Comments

简介

利用其庞大的 .NET 框架,Microsoft® 引入了一种叫做 ADO.NET 的新的数据访问技术。在本文中,我们将分析如何使用 Informix 的 ADO.NET 驱动程序,该程序包含在 IBM Client SDK 版本 2.90 中。所包含的示例代码是用 C# 编写的。

背景介绍

在使用 ADO.NET 驱动程序之前,应该确保该驱动程序已安装并能正确运行。该驱动程序的当前版本可以使用 Informix Client Software Developer's Kit (SDK) 2.90 来安装。与以前的 2.81 版本不同,此 SDK 版本包括默认的 ADO.NET 驱动程序。SDK 的安装程序也会警告您有关事项。它并不真正在您的计算机上查找 .NET 框架的安装,只是警告您必须在安装 SDK 之前安装好 .NET 框架。如果您已经安装了 2.81 SDK,那么最好先卸载它。这两个版本无法共存。还要认识到的一点是,在将 2.90 ADO.NET 驱动程序添加到 Visual Studio Projects 中时,它会不正确地报告它自己是版本 2.81。

2.9 版本是在 2.81 版本之上的一次重要升级。它包括一个新的 IfxDataAdapter 向导、IPv6 支持和一些用于 Informix 数据类型(IfxDateTime、IfxDecimal、IfxBlob 和 IfxClob)的新类。该文档更为完善,内容总量是以前的两倍。

要点:IBM Informix ADO.NET 驱动程序并不仅仅包含在安装目录下的 /bin 目录下的 IBM.Data.Informix.dll 文件中。显然,它使用了由 SDK 安装的其他客户端代码。这意味着您必须在所有将使用 ADO.NET 驱动程序的机器上安装 Informix Client SDK。您不能只在您的发行版中包括 IBM.Data.Informix.dll。这对一些应用程序而言可能是一个严重的限制。您还需要仔细检查 SDK 安装程序 (SetNet32),以定义 Informix 数据源。

在将 ADO.NET 驱动程序用于连接之前,还必须运行一个叫做 cdotnet.sql 的存储过程。这个存储过程位于 SDK 安装的 /etc 目录中。这类似于设置 OLEDB 驱动程序的过程,尽管这个过程更短一些。这个过程记录在 User's Guide 中。(请参阅下面的 参考资料 部分。)

在完成安装之后,检查一下驱动程序,确保建立了连接。要在 Visual Studio 项目中使用 ADO.NET 驱动程序,则必须确保已将一个引用添加到客户端 SDK 安装的 /bin 目录中找到的 IBM.Data.Informix.dll 中。正确的 using 语句是:using IBM.Data.Informix。以下是一个演示如何获得到数据库的连接的简单方法:

清单 1. 到 Informix 数据库的连接
public void MakeConnection() {
    string ConnectionString = "Host=" + HOST + "; " +
     "Service=" + SERVICENUM + "; " +
     "Server=" + SERVER + "; " +
     "Database=" + DATABASE + "; " +
     "User id=" + USER + "; " +
     "Password=" + PASSWORD + "; ";
    //Can add other DB parameters here like DELIMIDENT, DB_LOCALE etc
    //Full list in Client SDK's .Net Provider Reference Guide p 3:13
    IfxConnection conn = new IfxConnection();
    conn.ConnectionString = ConnectionString;
    try {
        conn.Open();
        Console.WriteLine("Made connection!");
        Console.ReadLine();
    } catch (IfxException ex) {
        Console.WriteLine("Problem with connection attempt: "
                          + ex.Message);
    }
}

示例代码中包括一个用于此功能的 BasicConnection 类。如您所见,ConnectionString 只是一个用于连接的分号分隔的参数列表。Open() 方法打开了到数据库的连接,如果连接失败,则抛出一个 IfxExceptionIfxException.Message 属性通常提供关于失败原因的合理数量的详细信息。

基本命令

一旦建立了连接,就可以开始对数据库执行命令。要做到这一点,需要使用 IfxCommand 对象。IfxCommand 的构造函数接收一个字符串(SQL 命令文本)和一个 IfxConnection 作为参数。IfxCommand 对象有一系列的 Execute 方法,以便对数据库执行命令。要清除连接,可以使用 IfxConnection.Close() 方法。以下是执行某个不返回结果集的简单命令的例子。该命令可能是 insert、update 或 delete。

清单 2. 执行 insert、update 或 delete 命令
IfxCommand cmd;
cmd = new IfxCommand("insert into test values (1, 2, 'ABC')",conn);
cmd.CommandTimeout = 200; //seconds to wait for command to finish
try {
    int rows = cmd.ExecuteNonQuery();
}
catch (IfxException ex) {
    Console.WriteLine("Error "+ex.Message);
}

ExecuteNonQuery 以整数形式返回受命令影响的行数。您还可以构建参数化语句和查询,后面部分将对它们进行研究。注意 IfxCommandCommandTimeout 属性。默认超时时间是 30 秒,尽管在文档中没有对此进行说明。除非更改此属性,否则运行 30 秒后,命令就会超时,并且将抛出一个异常。

下一个例子是执行一条 select 语句,并处理由数据库服务器返回的结果集。对于快速的、只向前通过结果的游标,可以使用由 ExecuteReader 方法返回的 IfxDataReader。不过,每个 IfxConnection 只可以有一个打开的 IfxDataReader。(这是一条 ADO.NET 限制,不是 Informix ADO.NET 驱动程序的特定限制。)

清单 3. 迭代通过 IfxDataReader
IfxCommand cmd = new IfxCommand("select * from test",bconn.conn);
try {
    IfxDataReader dr = cmd.ExecuteReader();
    while (dr.Read()) {
        int a = dr.GetInt32(0);
        int b = Convert.ToInt32(dr["b"]);
        string c = (String)dr[2];
    }
    dr.Close();
}
catch (IfxException ex) {
    Console.WriteLine("Error "+ex.Message);
}

每一列都被作为一般的 Object 类型进行检索。正如代码所演示的,存在一些将列 Objects 转换为正确数据类型的方法。您可以使用 IfxDataReaderGetXxx 方法。对于每种数据类型,几乎都有相应的方法。GetXxx 方法将列数目作为一个参数。可以使用 IfxDataReader 的索引,通过列的名称来访问列。如果可能的话,.NET 框架的 Convert 函数可以将这些 Objects 转换为正确的类型。最后,可以根据列编号为这些列建立索引,并直接强制转换结果(对于某些类型)。

下一个例子将展示如何调用需要一个参数值的存储过程。

清单 4. 执行带有一个参数的存储过程
IfxCommand cmd = new IfxCommand("test_proc",conn);
cmd.CommandType = CommandType.StoredProcedure; //from System.Data
cmd.Parameters.Add("in_parameter",2); //many ways to create these
try {
    cmd.ExecuteScalar();
}
catch (IfxException ifxe) {
    Console.WriteLine("Error "+ifxe.Message);
}

对于此 IfxCommand,必须将 CommandType 设置为来自 System.Data 中的 CommandType 枚举的 StoredProcedure 值。为了创建参数,可以使用 IfxCommandParameters.Add 方法。IfxCommands.Parameters 是一个集合,因此您可以添加您所需数量的参数。您可以使用任意 IfxParameter() 构造函数来创建参数,或者可以像上面这样简化参数的创建。不过要注意的是,每个 IfxParameter 都与一个特定的 IfxCommand 相关。您不能先创建 IfxParameters,然后在多个 IfxCommand 对象中使用它们。ExecuteScalar() 方法现在只返回 1。这一示例没有从存储过程返回任何东西。

要构建一个不执行存储过程的参数化 SQL 语句,需要将问号作为占位符插入 CommandText 中。例如:

清单 5. 参数化查询
IfxCommand insCmd = new IfxCommand("insert into clientstest " 
    + "(clientcode, clientacctname, primarycontact, primaddrcode, "
    + "initialamt,createdate) values (0,?,?,?,?,TODAY)",conn);

按照 IfxParameter 对象在命令文本中的顺序,将这些对象添加到 IfxCommandParameters 集合中。在下面的扩展示例中的最终强类型化 DataSets 中,将进一步演示此技术。

强类型数据集

ADO.NET 包括一个叫做 DataSet 的专用数据库对象。它是一个内存数据库。DataSet 由一个或多个(由一些 DataRow 对象的)DataTable 对象组成。DataTable 可以通过主键和外键相关联。可以对数据设置一些约束。DataSet 也与实际的数据存储断开连接,可以通过一个或多个 DataAdapter(每个 DataTable 一个)来填充它,然后在内存中保存这些数据和所有更改。稍后,DataAdapter 可以将这些更改提交回数据存储。

基本的 DataSet 不是强类型的。它不知道数据库的实际行和列是什么。因此编译器没有检查这些列名称。直到运行的时候,列名称中的任何错误才会显现出来。此外,当开发者记不清列名是 "itemcode" 还是 "itemid" 的时候,他会发现基本的 DataSet 在这方面毫无帮助。

一个强类型的 DataSet 可以解决这些问题。而一个普通的 DataRow 却无法取代它,因为普通的 DataRow 只有一个(例如)作为 OrderDetailDataTable 一部分的 OrderDetailDataRow。您可以将这些列作为 OrderDetailDataRow 的实际属性 (row.ItemCode) 进行引用。用这种方式,可以提高 IntelliSense 的生产率。表名称和列名称在属性编辑器中也会变得有效,从而增强诸如数据绑定之类的设计人员级工具。

那么,怎样构建这个提高生产率的强类型 DataSet 呢?要花费如此多的时间或精力来构建一个您还没有体验到任何净生产效率的东西吗?Informix ADO.NET 驱动程序可能没有其他一些驱动程序那么复杂。Microsoft 的 SQLDataAdapter(用于 SQL Server)包括一个 Generate DataSet 向导。而 IfxDataAdapter 没有这样的向导。不过,您可以构建一些工具来帮助您,也可以使用一些已在 .NET 框架中构建的工具。最后,您将拥有封装所有数据库交互的强类型 DataSet 的一个子代。

.NET 框架包括一个 XSD 编译器 (xsd.exe),它可以从某个经过特殊格式化的 .xsd 文件中生成一个强类型 DataSet。但是,谁想键入一串 XML 呢?幸运的是,DataSet 对象包括一个叫做 WriteXmlSchema() 的方法。此方法允许您使用非类型化的 DataSet 为强类型 DataSet 创建 XSD 文件。让我们来看一下如何做到这一点。以下是一个示例表:

清单 6. Clientstest 表
CREATE TABLE clientstest (
  clientcode SERIAL not null,
  clientacctname CHAR(60) not null,
  primarycontact CHAR(30) not null,
  primaddrcode CHAR(10),
  createdate DATE,
  initialamt DECIMAL (18,0)
);

以下是用于此表的单表 DataSet :

清单 7. 定义 DataSet
DS = new DataSet("dsClients");
//main table definition
DataTable mainTable = new DataTable("clients");
DataColumnCollection cols = mainTable.Columns;
DataColumn column = cols.Add("clientcode",typeof(Int32));
column.AllowDBNull = false;
cols.Add("clientacctname",typeof(String)).MaxLength = 60;
cols.Add("primarycontact",typeof(String)).MaxLength = 30;
cols.Add("primaddrcode",typeof(String)).MaxLength = 10;
cols.Add("initialamt",typeof(Decimal));
cols.Add("createdate",typeof(System.DateTime));
//primary key
mainTable.PrimaryKey = new DataColumn[] {cols["clientcode"]};
//add table to DataSet
DS.Tables.Add(mainTable);
//Write schema to file
DS.WriteXmlSchema("dsClients.xsd");

在这个定义中,可以在数据上设置类型和限制条件。还可以设置列名称。这些名称不必与数据库的列名称匹配。观察本文 下载 部分中的代码文件,以查看得到的 dsClients.xsd 文件。

为了使生成 XSD 文件(或者在发生更改后重新生成它)变得更容易,可以为这些 DataSet Builders 构建一个框架。(完成此任务所需的所有代码都包含在下面部分。)在想用该框架确定要构建哪些 Builders 时,可以使用反射来动态确定某一 Builders 是否是 DataSetBuilder。让我们从编写 IBuildable 接口开始。它定义了 DataSetBuilder 必须实现的属性和方法。

清单 8. IBuildable 接口
public interface IBuildable {
    string FileName {get; set;}
    string FilePath {get; set;}
    Logger Log {get; set;}
    DataSet DS {get; set;}
    void BuildXSD();
    void CompileXSD(string outputDirectory);
}

清单 7 中的代码(DataSet 的定义)主要是 BuildXSD() 方法。创建一个称为 DataSetBuilder 的抽象父类。BuildXSD 是必须在每个具体后续类中重写的抽象方法。对于每个 DataSetBuilder 而言,CompileXSD 方法是相同的,因此它位于 DataSetBuilder 中。以下是来自此抽象类的 CompileXSD() 方法:

清单 9. CompileXSD() 方法
log.LogStatus("Compiling "+filename);
ProcessStartInfo processinfo = new ProcessStartInfo();
processinfo.FileName = 
  @"C:\Program Files\Microsoft Visual Studio .NET 2003\"
  +@"SDK\v1.1\Bin\xsd.exe";
processinfo.Arguments = FilePath+FileName+" /dataset /namespace:"
  +ds.Namespace+" /out:"+outputDirectory;
processinfo.UseShellExecute = false;
processinfo.RedirectStandardInput = true;
processinfo.RedirectStandardOutput = true;
processinfo.RedirectStandardError = true;
processinfo.CreateNoWindow = true; //doesn't work
processinfo.WindowStyle = ProcessWindowStyle.Hidden; //doesn't work
Process compiler = Process.Start(processinfo);
log.LogStatus("Output:\n"+compiler.StandardOutput.ReadToEnd());
log.LogStatus("Error:\n"+compiler.StandardError.ReadToEnd());
compiler.WaitForExit();

此方法使用 ProcessProcessStartInfo 类来执行 XSD 编译器,这两个类来自 .NET 框架的 System.Diagnostics。此示例代码使用了来自 ObjectGuy 的、免费并且相关的 .NET Logging Framework(请参阅 参考资料)。

因为所有 DataSetBuilder 类都要实现 IBuildable 接口,所以我们可以使用反射来浏览程序集,并从 DataSetBuilders 构建所有 DataSet 类。这正是 DataLibraryBuilder 类要做的事情。例如,ClientsBuilder 被编译到一个 dsClients 类中。

生成的 dsClients 类是一个强类型 DataSet。从第 42 行的 ClientsBuilder 开始,我们现在几乎拥有 500 行强类型化代码。请查看这个生成的代码,它包含一个 clientsDataTableclientsDataTable 拥有每个列的属性。还有一些如下所示方法:NewclientsRow()IsinitialamtNull()FindByclientcode(int clientcode)。在使用这个类时,这些方法非常有用。

要将 Informix 数据库访问封装在强类型化 DataSet 中,需要继承 dsClients。这是您可以在应用程序中使用的 Clients 类。继承性提供了一些保护,防止在 DataSet 模式下发生更改。如果该模式发生更改,则可以重新生成 dsClients 类。Clients 类未被更改(尽管您可能也需要对此进行一些更改)。在 Clients 类中,可以为每个 DataTable 添加一个 IfxDataAdapter(在这种情况下,只能添加一个)。对于每个 IfxDataAdapter,需要定义 SQL 文本,并为 select、insert、update 和 delete 命令定义参数。然后可以重写 FillUpdate 方法,以初始化、填充和更新所有 IfxDataAdpaters。查看以下作为一个例子的 Insert 命令:

清单 10. IfxDataAdapter 的 InsertCommand
IfxCommand insCmd = new IfxCommand("insert into clientstest "
  +"(clientcode, clientacctname, primarycontact, primaddrcode, "
  +"initialamt,createdate) values (0,?,?,?,?,TODAY)",conn);
insCmd.Parameters.Add("clientacctname", IfxType.Char,60,
  "clientacctname");
insCmd.Parameters.Add("primarycontact", IfxType.Char, 30,
  "primarycontact");
insCmd.Parameters.Add("primaddrcode", IfxType.Char, 10,
  "primaddrcode");
insCmd.Parameters.Add("initialamt", IfxType.Decimal, 16,
  "initialamt");
daclients.InsertCommand = insCmd;

IfxDataAdapter 具有以下命令属性:SelectCommandInsertCommandDeleteCommandUpdateCommand。当 IfxDataAdapter 执行 Fill() 方法时,它会使用 SelectCommand 命令来查询数据库。当调用 Update() 方法时,IfxDataAdapter 使用 Insert、Update 和 Delete 命令的组合来使数据库与表的内存版本相一致。IfxDataAdapter 决定哪些行和列需要更改;开发人员不必编写代码来追踪数据的更改。

注意,可以将零插入序列值,这通常适用于 Informix 串行类型。但是,如何可以让数据库生成的值回到断开连接的 DataSet 中呢?您不必陷入 dsclientsIfxDataAdapterRowUpdated 事件。在该事件的处理器中,代码看起来像是随意插入的。对于某个插入对象,执行 dbinfo 命令可以检索刚创建的序列值。处理器将该值放入 DataRow 的客户机代码列。以下是特定于此 Informix 技巧的事件处理器代码:

清单 11. 检索生成的序列值
private void daclients_RowUpdated(object sender, 
                                  IfxRowUpdatedEventArgs e) {
    //For INSERTs only, gets the serial id and 
    //inserts into the clientcode
    if (e.StatementType == StatementType.Insert) {
        IfxCommand getSerial = new IfxCommand(
          "select dbinfo('sqlca.sqlerrd1') from systables "
          +"where tabid = 1",
          daclients.InsertCommand.Connection);
        e.Row["clientcode"] = (int)getSerial.ExecuteScalar();
    }
}

结束语

现在,您有一个完全封装的业务对象,它可以处理自己的数据库交互。那么现在您可以做些什么呢?因为 DataSet 衍生自 System.Component,所以您可以在 Visual Studio Toolbox 上添加强类型 DataSet 对象(例如,Client)。然后,您可以将它拖动到任何 WinForm 或 WebForm 设计视图上。请为 Connection 设置属性。在您的代码中执行对象的 Fill() 方法(可能在 FormLoad 事件中)。这会使用每个 DataTable 的所有数据填充该对象。在 Designer 视图中,还可以通过设置视觉控件或网格的 DataSource(以及 DataMember 属性)来实现数据绑定。

示例解决方案中的 LibraryConsoleTest 程序演示了强类型 DataSet 的工作方式。您现在可以编写如下所示的代码:Console.WriteLine(client.clientcode + " " + client.clientacctname+" " + client.createdate);,而不是编写以下代码:Console.WriteLine(ds.Tables["clients"].Rows["clientcode"] + " " + ds.Tables["clients"].Rows["clientacctname"] + " " + ds.Tables["clients"].Rows ["createdate"] )。LibraryConsoleTest 添加了一台新客户机,并检索生成的序列号。在使用 Findbyclientcode() 方法选择正确的客户机之后,现在它要删除一台客户机。它还要更新特定行中的某一列。最后,它要循环遍历客户机,将数据输出到 Console。示例解决方案还包括一个快速 Windows Forms 应用程序 (WinFormsApp),它演示了如何使用 DataGrid 实现数据绑定。

对于大多数业务应用程序而言,数据访问是恒古不变的需要。然而,实现数据访问的模型和方法一直在变。本文中的例子将帮助您了解是否选择 ADO.NET 和 Informix Dynamic Server 作为您的工具。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=98237
ArticleTitle=利用 ADO.NET 连接到 Informix
publish-date=11072005