数据库-使用Command对象进行数据库查询

 

ADO.NET中使用Command对象来与数据库进行查询.

一、创建Command对象

有三种方式创建Command对象:

1.7         第一种使用New关键字,调用Command对象的无参构造子,再进行属性设置

1.8         第二种也是使用New关键字,但使用的构造子提供参数来指定查询字符串和Connection对象

1.9         第三种通过Connection对象的CreateCommand方法

二、无返回查询

1          操作查询

不返回结果集的查询通常称为操作查询(action query)

2          操作查询有2种主要类型

2.1         使用数据操纵语言(DML)查询

也称为基于查询的更新,该类型会对数据库进行更新

1

Update Customers SET CompanyName='NewCompanyName' WHERE CustomerID='ALFKI'

2

INSERT INTO Customers (CustomerID,CompanyName) VALUES('NewID','NewCustomer')

3

DELETE FROM Customers WHERE CustomerID='ALFKI'

2.2         数据定义语言(DDL)查询

此种类型常用于更改数据库结构

1

CREATE TABLE table1 (Field1 int NOT NULL CONSTRAINT PK_Table1 PRIMARY KEY,

                                          Field2 varchar(32))

2

ALTER VIEW View1 AS SELECT Field1,Field2 FROM table1

3

DROP PROCEDURE StoredProcedure1

3          VBNET中使用无返回查询代码演示

Dim cn As New OleDbConnection()

cn.ConnectionString=<>

cn.Open()

Dim cmd As OleDbCommand=cn.CreateCommand()

cmd.CommandText="UPDATE Customers SET CompanyName='NewCompanyName' "&_

                                   " WHERE CustomerID='ALFKI'"

cmd.ExecuteNonQuery()

4          关于"无返回查询"(操作查询)的讨论

4.1         操作查询是一种很有效的查询.只是它们不返回行.

4.2         执行操作查询可能会导致失败.例如:

CREATE TABLE table1 (Field1 int NOT NULL CONSTRAINT PK_Table1 PRIMARY KEY,

                                          Field2 varchar(32))

1.1.7                    该查询失败的原因:

l         已经存在同名的表格

l         查询语法错误

l         没有与数据库之间的开放连接

1.1.7                    如果执行的查询没有发生错误,那么就意味着成功的创建了新的表格

4.3         由于操作查询是设计用来修改或删除现有行的,更新的操作也可能导致失败

UPDATE Customers SET CompanyName='NewCompanyName' WHERE CustomerID='ALFKI'

1.1.7                    无法正确执行可能会因为该行已经被另外一个用户从表中删除.

数据库能够执行该查询,但是由于没有满足WHERE子句条件的行,所以查询不会作出修改行为.

对于数据库来说,这样的结果并不表示失败.

1.        如何辨别查询是否修改了行呢?

Sql Server的查询分析器中执行,会得到信息:(1 rows affected)

Command对象则将查询所影响的行数作为ExecuteNonQuery方法的返回值.

2.        代码演示

Dim strConn As String=<连接字符串->

Dim cn As New OleDbConnection(strConn)

cn.Open()

Dim cmd As OleDbCommand=cn.CreateCommand()

cmd.CommandText="UPDATE Customers SET CompanyName='NewCompanyName' " &_

                                   "WHERE CustomerID='ALFKI'"

Dim recAffected As Integer=cmd.ExecuteNonQuery()

If recAffected=1 Then

       Console.WriteLine("Update Succeeded")

Else

       Console.WriteLine("Update Failed")

End If

4.4         特别注意

如果执行的是非DML查询,那么ExecuteNonQuery方法返回值是-1.

三、用DataReader对象检查查询结果

1.        执行一个返回结果集的查询

Command对象提供ExecuteReader方法,返回值是DataReader对象,可以通过该对象检查查询结果

2.        DataReader对象与.NET其他读取程序的对象(XMLReader/TextReader/StreamReader)使用起来没有区别

3.        代码演示

...(省略-打开Connection对象cn的过程)...

Dim cmd As New OleDbCommand("SELECT CustomerID,CompanyName FROM Customers",cn)

Dim rdr as OleDbDataReader=cmd.ExecuteReader()

While rdr.read()

       Console.WriteLine(rdr("CustomerID") & "-" & rdr("CompanyName"))

End While

rdr.Close()

1.1         解释

1.1.1                    关于DataReader对象的Read方法

只有在你调用Read方法之后,数据的第一行才可用,所以循环中要在显示之前就调用Read方法

调用一次Read方法,DataReader会自动移动到下一行.

Read方法返回值为布尔类型,表示下一行是否可用.

1.1.2                    更快的获取结果集内容

DataReader对象的Item属性(这是个默认参数化属性)

Console.WriteLine(rdr("CustomerID") & "-" & rdr("CompanyName"))

这里就使用了Item属性,这样的代码效率较低.

1.        提高此处代码性能的办法

1.1.2.1.1                                使用基于序号的查找

前面的代码段使用列名称查找Item属性,这将引发结果集中每一行都执行一个基于字符串的查找.

如果改为提供列的索引或序号,将避免对字符串的查找,从而提高性能.

此项技巧可应用于所以公开集合的对象.

DataReader对象可以根据列名称取得其索引(GetOrdinal方法).这样你可以使用它来在循环之前取得列名在集合中的索引值,从而加速查找性能.

l         修改后代码演示

Dim rdr As OleDbDataReader=cmd.ExecuteReader()

Dim customerIDOrd As Integer=rdr.GetOrdinal(“CustomerID”)

Dim CompanyNameOrd As Integer=rdr.GetOrdinal(“CompanyName”)

While rdr.Read()

       Console.WriteLine(rdr(customerIDOrd) & “_” & rdr(companyNameOrd))

End While

rdr.Close()

1.1.2.1.2                                使用适当的类型指定Get方法

DataReader对象提供了一系列的方法可以用来返回符合.NET类型的数据
(
包括:String/Int32/decimal/Double)

大多数集合对象都隐含使用了Item属性,其返回值是Object类型.显示其中所存储的值一般需要转换为字符串(ToString方法)

然而我们也可以使用DataReader对象提供的GetXXX方法来指定返回值类型.

应该保持一直使用与结果集中列返回的数据相对应的类型指定的Get方法的好习惯,即使我们需要在界面控件中使用字符串,也不例外

l         修改后代码演示

Dim rdr As OleDbDataReader=cmd.ExecuteReader()

Dim CustomerIDOrd As Integer=rdr.GetOrdinal(“CustomerID”)

Dim CompanyNameOrd As Integer=rdr.GetOrdinal(“CompantName”)

While rdr.Read()

Console.WriteLine(rdr.GetString(CustomerIDOrd) & “_” &
 rdr.GetString(CompanyNameOrd))

End While

1.1.3                    获取多个结果-批查询

某些数据库(比如SQL SERVER)支持执行返回多个结果的批查询.

假设有如下查询:

       SELECT CustomerID,CompanyName,ContactName,Phone FROM Customers;

       SELECT OrderID,CustomerID,EmployeeID,OrderData FROM Orders;

       SELECT OrderID,ProcedureID,Quantity,UnitPrice FROM [Order Details]

使用前面的方法,DataReader对象在循环中Read(),只能在批查询中循环第一个查询的结果.

解决这个问题需要使用DataReader对象提供的NextResult方法,该方法可以使你移动到下一个返回行的查询结果上去.另外,该方法返回一个布尔类型,但是它不可以向Read方法那样在循环之前调用.

l         使用NextResult()的代码演示

cn.Open()

Dim ssql as string=” SELECT CustomerID,CompanyName,ContactName,Phone FROM Customers;

       SELECT OrderID,CustomerID,EmployeeID,OrderData FROM Orders;

       SELECT OrderID,ProcedureID,Quantity,UnitPrice FROM [Order Details]”

Dim cmd as new OleDbCommand(ssql,cn)

Dim rdr as OleDbDataReader=cmd.ExecuteReader()

Do

       Do While rdr.Read()

              Console.WriteLine(rdr(0) & “_” & rdr(1))

       Loop

       Console.WriteLine()

Loop While rdr.NextResult()

1.1.4                    执行成批的操作查询

使ADOSQL SERVER来获取由存储过程生成的结果集经常会存在以下问题.

调用使用SQL Server OLE DB提供者的Sql Server存储过程,并且此存储过程在返回行的查询之前执行了一次查询,那么Recordset会被标记为关闭”,从而导致返回的记录集不包含返回行的查询结果.

这个问题是有设计造成的.

解决办法有两个:

1.        使用NextRecordset方法

DataReader对象的这个方法能使(当前光标)移动到下一查询的结果上去.

因为:关闭的Recordset与查询操作相对应.更严格的说它是与查询所返回的信息性消息影响X相对应的

2.        在存储过程中添加SET NOCOUNT ON语句

这个语句的作用是取消影响N消息,从而能够是ADO(光标)立即移动到第一个返回行的查询结果上.

l         该操作不仅存在于存储过程.

l         通过执行批查询,也可以创建类似的情况

l         代码演示(ADO2.0VB经典代码)

Dim cn as ADODB.Connection,rs as ADODB.Recordset

Dim strConn as string, strSql as string

Dim RecAffected as Integer

strConn=”Provider=SQLOLEDB; Data Source={local}"NetSDK;
              Initial Catalog=Northwind; Trusted_Connection=Yes;”

Set cn=New ADODB.Connection

Cn.Open strConn

strSql=”insert into Customers …;”&_
              “select CustomerID,CompanyName from Customer where …;”&_
              “update Customers set CompanyName=… where…;”&_
              “select CustomerID,CompanyName from Customer where …”

Set rs=cn.Execute(strSql,recAffected,adCmdText)

Do Until rs Is Nothing

       Debug.Print “rs.State=” & rs.State & vbTab &_
                     “records affected=” & recAffected

       Set rs=rs.NextRecordset(recAffected)

Loop

l         代码运行分析

循环中,初始状态下,recordset是关闭的,并且recAffected=1.原因是这个结果对应insert查询,该查询不返回行.而是对数据库记录的修改

一旦调用了NextRecordset方法后,recordset的状态就变成开放的了.并且其中包含了第一个select查询的结果,由于select查询不修改任何记录,所以第一个select查询之后,recAffected=-1

然后第二次调用NextRecordset方法,它将返回一个关闭的数据集,recAffected保存了Update查询影响的行数.

然后在第三次调用NextRecordset方法是返回的是第二个select查询的结果,recAffected有等于-1.

最后还要调用一次NextRecordset(),此时返回一个设置为Nothingrecordset,表明没有其他的结果需要处理.

3.        ADONET处理上例的批查询

ADONET处理批查询的方式与ADO不同.

(为了简化批查询处理的过程)DataReader会自动移动到第一个返回行的查询结果处(第一个select查询返回行)

但是,这个改进仍然有些顾此失彼的地方.

DataReader对象没有提供能够检查每个单独的操作查询所影响行数的方法.

DataReader对象的RecordsAffected属性保存的是运行总计.

l         代码演示

Dim strConn as string=”Provider=SQLOLEDB; Data Source={local}"NetSDK;
              Initial Catalog=Northwind; Trusted_Connection=Yes;”

Dim cn as new OleDbConnection(strConn)

Cn.Open()

Dim strSql as string=”insert into Customers …;”&_
              “select CustomerID,CompanyName from Customer where …;”&_
              “update Customers set CompanyName=… where…;”&_
              “select CustomerID,CompanyName from Customer where …”

Dim cmd as new OleDbCommand(strSql,cn)

Dim rdr as OleDbDataReader=cmd.ExecuteReader()

Do

       Console.WriteLine(“影响行数=” & rdr.RecordsAffected)

       Do While rdr.Read()

              Console.WriteLine(vbTab & rdr.GetName(0) & “ - ” & rdr.GetValue(0))

       Loop

Loop While rdr.NextResult()

l         代码分析

ADONET的代码和ADO代码很相似.但是,结果略有不同.

当通过Command对象取得DataReader对象之后,DataReader对象会立即返回第一个select查询的结果

第一次调用NextResult方法时,它立即移动到第二个select查询的结果

第二次调用NextResult(),直接返回false,从而跳出循环.

在性能上,DataReader对象的RecordsAffected属性直接返回所以操作查询影响的记录总和.

实际运行中,ExecuteReader方法返回DataReader对象时,RecordsAffected属性=1;

当调用NextResult方法后RecordsAffected属性=2

l         切记

DML操作查询(CREATE PROCEDURE DROP TABLE)返回行数总是-1

1.1.5                    关闭DataReader对象

ADONET对象模型中,记住尽可能快速的关闭DataReader对象,这非常重要.

目前为止,一个带着开发的DataReaderConnection对象仍被认为是锁定的.

如果在关闭DataReader对象之前,试图打开第二个,会导致异常.”需要一个开放并可用的连接

RDO中存在相同的限制.

ADO(Sql Server)打开两个流水游标则很正常.

此类情形,可以归结为:试图使用一个忙连接.

微软数据访问技术的每一代对于此情况处理都不相同.

l         VBSQL生成错误

l         DAO/Jet创建新连接

l         RDO生成错误

l         ADO创建新连接

l         ADO.NET生成错误

1.        使用DataReader对象是基于性能方面的原因,应该在发出查询之后尽快地将结果拉下来

2.        开发的DataReader对象会锁定Connection对象

3.        如果需要在各查询的结果之间前后移动,应该使用DataSet对象;
或者考虑将查询结果存储在某类业务对象之中.

1.1.6                    执行返回单值的查询

查询举例:

Select COUNT(*) from Customers

Select CompanyName from Customers where CustomerID=’ALFKI’

如上查询,DataReaderDataSet都有点大材小用.

Command对象提供了一个方法(ExecuteScalar)专门获取单值查询.其返回值类型为Object

l         代码演示

Dim strConn as string=”Provider=SQLOLEDB; Data Source={local}"NetSDK;

              Initial Catalog=Northwind; Trusted_Connection=Yes;”

Dim cn as new OleDbConnection(strConn)

cn.Open()

Dim cmd as OleDbCommand=cn.CreateCommand()

cmd.CommandText=”select COUNT(*) from Customers”

Dim customerCount as Integer=Cint(cmd.ExecuteScalar())

cmd.CommandText=”select CompanyName from Customers where CustomerID=’ALFKI’”

Dim companyName1 as string Convert.toString(cmd.ExecuteScalar())

1.1.7                    参数化查询

假设:建立一个应用程序,用户可检查客户端放置的顺序.

如果你有20个客户端,不需要编写20个独立的函数来返回客户端的顺序,只要建立参数化查询.并使它可接收有关客户的信息并为该客户返回其顺序即可.同样,还可以建立接收参数的查询,

SELECT OrderID,CustomerID,EmployeeID,OrderDate FROM Orders WHERE CustomerID=?

l         其中问号?是一个参数标记,(所谓参数标记是指一种在查询中指示参数的标准方式)

l         SQL SERVER.NET数据提供者不支持通用的参数标记”?”,它需要的是带有@前缀的命名参数.

SELECT OrderID,CustomerID,EmployeeID,OrderDate FROM Orders WHERE CustomerID=@CustomerID

1.1.7.1      ADO.NETParameter对象

创建ADO.NETParameter对象即可存储参数信息的一种结构.

一般的,创建Command对象,向其Parameters集合属性中添加一个Parameter,并给它提供值,最后执行Command对象,以便获取特定客户的顺序信息.

l         代码演示

Dim strConn,strSql As string

strConn=”Provider=SQLOLEDB; Data Source=(local)"NetSDK; Initial Catalog=Northwind;”&_
“ Trusted_Connection=Yes;”

Dim cn As New OleDbConnection(strConn)

Cn.Open()

strSql=”Select OrderID,CustomerID,EmployeeID,OrderData from Orders where CustomerID=?”

Dim cmd As New OleDbCommand(strSql,cn)

cmd.Parameters.Add(“@CustomerID”, OleDbType.Wchar,5)

cmd.Parameters(0).Value=”ALFKI”

Dim rdr As OleDbDataReader=cmd.ExecuteReader()

l         总结

使用参数化查询可以大幅度简化编程.

更改参数的值要比通过编程连接查询字符串要容易得多.特别是,在为某参数复制的时候,无需再考虑去删除它的问题.

1.1.8                    调用存储过程

假设有一个存储过程,它能返回一行数据.如下(Sql Server中的)

CREATE PROCEDURE GetCustomer(@CustomerID nchar(5) AS

SELECT CustomerID,CompanyName,ContactName,ContactTitle

FROM Customers WHERE CustomerID=@CustomerID

                     RETURN

1.1.8.1      Command对象调用存储过程有两个方法

1.1.8.1.1                                方法之一:使用Command对象的CommandType属性

CommandType属性是CommandType枚举类型的值.

取值如下:TextTableDirectStoredProcedure

默认值Text

CommandType属性设置为StoredPriocedure,表示正在调用一个存储过程.(Command对象将CommandText属性的值和Parameters集合中的信息相结合,生成调用存储过程的语法)

l         示例代码

Dim cn As New OleDBConnection(strConn)

Cn.Open()

Dim cmd as OleDbCommand=cn.CreateCommand()

With cmd

       .CommandText=”GetCustomer”

       .CommandType=CommandType.StoredProcedure

       .Parameters.Add(“@CustomerID”,OleDbType.Wchar.5)

       .Parameters(0).Value=”ALFKI”

End With

Dim rdr As OleDbDataReader=cmd.ExecuteReader()

If rdr.Read() then

       Cosole.WriteLine(rdr(“CompanyName”))

Else

       Console.WriteLine(“No Customer Found.”)

endIF

rdr.Close()

cn.Close()

l         总结

这种语法是调用存储过程的标准方法:

{?=CALL MyStoredProc(?,?,?)}

或者不准备使用返回值(就是?),形式如下:

{CALL GetCustomer(?)}

比较推荐这种语法,而不是向上面代码中那样依赖与CommandType属性.

可能出现问题的地方是:如果你的查询中一个名称包含了特殊字符(比如空格),那么将CommandType属性设置为TableDirectStoredProcedure,对象名称中的分隔符无法识别.但是,SqlCommand对象可以准确的确定对象名称的间隔.

1.1.8.1.2                                第二种方法(也是推荐的方法)不要依赖CommandType属性,尽量在CommandText属性中为查询使用恰当的语法.

l         示例代码

Dim cmd As New OleDbDataAdapter()

cmd.CommandText=”{CALL GetCustomer(?)}”

cmd.CommandType=CommandType.Text

l         总结

SQL Server中在Query Analyzer工具中使用EXEC语法执行存储过程,也可以在Command对象中使用;但是,并非所有的数据库都支持该语法.

1.1.9                    从输出参数中获取数据

并非所有的存储过程都通过结果集返回信息

许多过程是通过输出参数来返回信息的

l         举例

CREATE PROCEDURE GetCustomer(@CustomerID nchar(5),

                                                               @CompanyName nvarchar(40) OUTPUT,

                                                               @ContactName nvarchar(40) OUTPUT,

                                                               @ContactTitle nvarchar(30) OUTPUT) AS

              SELECT @CompanyName=CompanyName,@ContactName=ContactName,

                            @ContactTitle=ContactTitle

              FROM Customers WHERE CustomerID=@CustomerID

IF @@ROWCOUNT=1

       RETURN 0

ELSE

       RETURN -1

1.1.9.1      如何利用Command对象从输出参数中获取数据?

Parameter对象有一个Direction属性,能接收ParameterDirection枚举类型的值

(ParameterDirection枚举值可能是ReturnValue,Input,InputOutput,Output)默认是Input

要从存储过程中获取信息,需要对Parameter参数的Direction属性进行设置

l         示例代码

...

Dim cn As New OleDbConnection(strConn)

cn.Open()

Dim cmd As OleDbCommand=cn.CreateCommand()

With cmd

       .CommandText=”{?=CALL GetCustomer(?,?,?,?)}”

       .Parameters.Add(“@RetVal”,OleDbType.Integer)

       .Parameters.Add(“@CustomerID”,OleDbType.WChar,5)

       .Parameters.Add(“@CompanyName”,OleDbType.VarWChar,40)

       .Parameters.Add(“@ContactName”,OleDbType,VarWChar,40)

       .Parameters.Add(“@ContactTitle”,OleDbType.VarWChar,30)

       .Parameters(“@ContactTitle”).Direction=ParameterDirection.Output

       .Parameters(“@RetVal”).Direction=ParameterDirection.ReturnValue

       .Parameters(“@CustomerID”).Value=”ALFKI”

       .Parameters(“@CompanyName”).Direction=ParameterDirection.Output

       .Parameters(“@ContactName”).Direction=ParameterDirection.Output

       .ExecuteNonQuery()

       If Convert.ToInt32(.Parameters(“@RetVal”).Value=0) Then

              Console.WriteLine(“找到”)

       Else

              Console.WriteLine(“没有记录”)

       End If

End With

1.1.10                Transaction中执行查询

Command对象有Transaction属性,要在Transaction中执行Command需要设置该属性.

Command对象调用BeginTransaction方法创建一个Transaction对象.

l         示例代码

...

cn.Open()

Dim txn As OleDbTransaction=cn.BeiginTransaction()

Dim strSql As String=”insert into Customers (...) VALUES (...)”

Dim cmd As New OleDbCommand(strSql,cn,txn)

Dim recAffected As Integer=cmd.ExecuteNonQuery()

If recAffected=1 Then

       Console.WriteLine()

       txn.Commit()

Else

       Console.WriteLine()

       txn.Rollback()

End If

 

 

 

posted @ 2008-09-16 16:52  怒杀神  阅读(1351)  评论(0编辑  收藏  举报