65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 的中级 OLE DB 消费者

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (2投票s)

2002年1月17日

8分钟阅读

viewsIcon

98536

downloadIcon

1149

使用 OleDbDataAdapter 和 OleDbCommands 对象来消费 OLE DB 提供程序

引言

这是 C# 关于 OLE DB 系列文章的第二篇。本文的功能与第二篇 C++ 文章 "C++ 中级 OLE DB 消费者" 完全相同,只是 C# 实现鼓励使用独立的命令对象,然后通过继承自 IAccessorOleDbReader 类来访问数据,根据其行为,我认为它在功能上与 C++ 中的 CDynamicAccessor 工作方式相同。

演示代码是在 Windows 2000 和 Dev Studio 7 Beta 2 上编写的

使用适配器

C# 实现通过 OleDbDataAdapter 访问数据库。可以从工具栏菜单中访问它,它会要求您选择一个之前使用过的连接,或者您可以使用向导指定一个新的连接。它会在您的类中生成六个私有成员。

private System.Data.OleDb.OleDbDataAdapter oleDbDataAdapter1;
private System.Data.OleDb.OleDbCommand oleDbSelectCommand1;
private System.Data.OleDb.OleDbCommand oleDbInsertCommand1;
private System.Data.OleDb.OleDbCommand oleDbUpdateCommand1;
private System.Data.OleDb.OleDbCommand oleDbDeleteCommand1;
private System.Data.OleDb.OleDbConnection oleDbConnection1;
        

代码实际上并没有使用 OleDbDataAdapter 变量,因为所有必要的命令都是通过向导生成的 OleDbCommands 来执行的。它本质上是代码和数据之间的桥梁。oleDbDataAdapter1 成员具有指向 Select、Insert、Update 和 Delete OleDbCommands 的引用,这样任何执行的命令都可以正确地通过数据库。这就是为什么在代码示例中您会看到以下行。

oleDbDataAdapter1.DeleteCommand = oleDbDeleteCommand2;
        

这会将向导设置的 Delete 命令更改为代码设置的新命令。

默认代码

首先需要明确的是,向导生成的默认代码应该被视为一种工作指南,而不是一个确定的工作方式。原因是,在我看来,默认代码比程序员自己指定一个命令变量更难使用。在查看默认代码时,有两个主要问题需要注意。

  1. 以下是 Insert 命令的生成代码。
    this.oleDbInsertCommand1.CommandText = 
        "INSERT INTO Books([Author First Name], [Author Last Name], Category, ID, price," +
        " Title) VALUES (?, ?, ?, ?, ?, ?); SELECT [Author First Name], [Author Last Name]," +
        " Category, ID, price, Title FROM Books WHERE (ID = ?)";
            

    第一个问题是,如果我们暂时忽略问号行,插入语句后紧跟着的是 Select 语句。这不仅是因为将一行插入数据库后,您不一定想只查看插入的那一行,而且并非所有数据库都支持这种语法。例如,Access 数据库如果使用向导生成的代码尝试插入或删除内容,代码将抛出异常,告知您 SQL 语句末尾有额外的文本。

  2. 第二个问题,从某种程度上来说是可以理解的,因为向导在生成代码时必须编写成能够生成您选择的任何数据库表的代码,那就是生成代码强制使用参数化查询。我本身并不反对参数化查询,但在这种情况下,使用它们只会使事情比实际需要更复杂。

    对于不知道参数化查询的人来说,其思想是您可以设置一个 SQL 语句,并在语句中使用问号作为占位符,稍后填充这些值。以生成的 Update 命令为例,它会给出以下 SQL 代码,

    this.oleDbUpdateCommand1.CommandText = 
        @"UPDATE Books SET [Author First Name] = ?, [Author Last Name] = ?, Category = ?," + 
        @"ID = ?, price = ?, Title = ? WHERE (ID = ?) AND ([Author First Name] = ?) AND " +
        @"([Author Last Name] = ?) AND (Category = ?) AND (Title = ?) AND (price = ?); " +
        "SELECT [Author First Name], [Author Last Name], Category, ID, price, Title " +
        "FROM Books WHERE (ID = ?)";

    这是代码用于更新记录的默认命令。此命令中的每个问号都必须设置一个占位符,以便稍后可以填充。这通过使用以下方式完成:

    this.oleDbUpdateCommand1.Parameters.Add(
        new System.Data.OleDb.OleDbParameter("Author_First_Name", 
                                                  System.Data.OleDb.OleDbType.Char, 50, 
                                                  System.Data.ParameterDirection.Input, 
                                                  false, 
                                                  ((System.Byte)(0)), 
                                                  ((System.Byte)(0)), 
                                                  "Author First Name", 
                                                  System.Data.DataRowVersion.Current, 
                                                  null)
                                            );
            

    每个占位符都是一个 OleDbParameter,存储在集合类的 Parameters 成员中。Parameters 成员是 OleDbParameterCollection 类型的变量。参数以所有必需的关于参数的信息(如其名称、大小和写入方向)一起放入连接中。这必须对 SQL 语句中的每一个问号都执行。而不是编写代码来填充所有这些参数,并且因为我想演示如何填充生成查询中的参数,所以我将 SQL 语句编辑为:

    this.oleDbUpdateCommand1.CommandText = "UPDATE Books SET [Author First Name] = ?, " +
                                           "[Author Last Name] = ?, Category = ?, " +
                                           "price = ?, Title = ? WHERE (ID = ?)"

    这完美地完成了我需要的功能,即更新所有可更新的变量。ID 值无法更新,因为它是一个由数据库控制的自动编号。这意味着要进行更新,我只需要填充行的值,然后告诉数据库我要更新哪个 ID 或哪一行。填充参数的代码是:

    OleDbParameterCollection colParams = oleDbUpdateCommand1.Parameters;
    
    try
    {
        IEnumerator enumParams = colParams.GetEnumerator();
        
        enumParams.MoveNext();
        OleDbParameter param = ( OleDbParameter )enumParams.Current;
        if( param.ParameterName == "Author_First_Name" )
            param.Value = m_AuthorFirstName.Text;
    
            

    这填充了第一个参数。它首先从 OleDbUpdateCommand1 变量获取 OleDbParameterCollection,然后从中获取集合的 IEnumerator 接口,该接口允许它一次移动一个项目。与我们稍后将遇到的 OleDbDataReader 一样,枚举器只能向前移动,并且在刚检索时位于集合的开头之前的位置,这就是调用 MoveNext 以在从枚举器获取 OleDbParameter 的原因。一旦获得 OleDbParameter,代码就会检查它是否具有我们期望的第一个参数的 ParameterName,如果它是 OleDbParameter,则将其 Object 成员值设置为它。由于 C# 中所有类型都继承自 Object,所以这不成问题。这里最大的潜在错误在于代码在写入数据库之前知道对象是什么类型。这由生成代码处理,通过设置 OleDbParameter 来完成,它不仅存储对象的类型,而且如果对象恰好是字符串(如此处),还会存储其最大长度。

    填充参数后,下一步是关闭您当前正在访问要更新的表的任何 OleDbReaders。如果您忘记这样做,代码将抛出异常,提醒您这是个好主意。然后代码调用

    int nAffected = oleDbUpdateCommand1.ExecuteNonQuery();

    这是在不期望函数返回结果时调用的函数

    查询执行后,需要重置 OleDbDataReader

    oleDataReader.Close();
    
    oleDataReader = oleDbSelectCommand1.ExecuteReader();
    
    for( int i=0; i<nRecordCount-2; i 
    {
        oleDataReader.Read();
    }
    
    MoveNext();
    
    nRecordCount -=1; 
            

    上面的代码显示了 Prev_Click 方法的内容,这有点取巧,但它通过跟踪 OleDbDataReader 当前所在的记录数,并在通过 Update、Delete 和 Insert 按钮或 View Authors 和 View Categories 按钮更改内容时重新定位行,从而使代码保持一致的外观。

使用自定义查询

我上面提到过,生成代码应该被视为您想要做的事情的模板,而不是一套严格的规则,并且我展示了如何稍微修改生成代码以使任务更容易。这个过程的下一步当然是使其更加容易,我的做法是动态构建 SQL 语句然后执行它们。Insert_ClickDelete_Click 函数都执行此操作。

string strSQL = "DELETE * FROM Books WHERE ID = ";

strSQL += m_ID.Text;

Delete_Click 方法简单地删除带有传入 ID 的所有内容,而 Insert_Click 函数必须确保行内的字段名称正确。请注意,此处未对文本的长度进行检查。如果字符串太长,将有一个 OleDbException 等待告知您。

/// set the sql in the command
oleDbDeleteCommand2.CommandText = strSQL;

oleDbDataAdapter1.DeleteCommand = oleDbDeleteCommand2;
/// no connection exception if you dont do this
oleDbDeleteCommand2.Connection = oleDbConnection1;

oleDbDeleteCommand2.ExecuteNonQuery();

oleDataReader = oleDbSelectCommand1.ExecuteReader();
        

执行查询的代码非常简单,首先,将设置好的 SQL 字符串传递给正在使用的 OleDBCommandCommandText 成员。在这种情况下是 OleDbDeleteCommand2。然后,将 Command 设置为 OleDbDataAdapter 成员的默认 DeleteCommand。最后,我们将使用的 Connection 设置为正在执行命令的 Connection。

完成此操作后,剩下的就是执行命令然后重置 reader,使一切恢复原样。

最后

需要注意的是,C# 会允许您添加一个具有重要函数名称的变量,然后拒绝编译,因为它无法确定您想做什么。我遇到了这个问题,添加了一个名为 update 的按钮,该按钮与按钮类继承的 System.Update 方法冲突。这意味着我不得不修改所有对 Update 变量的引用为 m_bUpdate,使用 MFC 风格的语法来区分它。

好了,我们已经涵盖了基础知识,现在可以开始处理重要的事情了。虽然我承认在这方面我预计 C# 不会有什么大问题,因为它到目前为止已经处理得相当好了。这主要是因为它与组件对象模型(Component Object Model)兼容,允许它访问构建较新版本 Windows 的强大接口集。

© . All rights reserved.