ADO.NET开发最佳实践

ADO.NET架构

使用ADO.NET连接最佳实践

为什么为连接池?

     创建连接所花费的时间与资源并不是无价值的。

     Connection pools可以使在特定页面运行过后,连接能够保持下来

ADO.NET中的连接池

         如果使用的是 OleDbConnection 类,则连接池将由提供程序自动处理,您不必自己进行管理。

         如果使用的是 SqlConnection 类,则连接池被隐式管理,但也提供选项允许您自己管理池。

     在连接字符串中指定:

         pooling =true;//默认为true

         connection lifetime=5;//默认为0

         min pool size=1;//默认为0

         max pool size=50“;//默认为100

 

[参考代码]

//以下代码是计算在使用连接池技术和不使用连接池技术的差别

//推荐使用Windows集成认证并使用内建服务帐号,这样是因为sa帐号的权限最大,当查

//询条件是一个SQL语句的话,非法用户就能够自己创建ADMIN权限的用户,给系统造成

//损失。

string strConUnusePool = "Server=localhost; Integrated Security=SSPI; database=mydatabase;pooling=false";

string strConusePool = "Server=localhost; Integrated Security=SSPI; database=mydatabase;"

                            +"pooling=true;connection lifetime=5";

int nConNum = 50;

//不使用连接池技术

DateTime dtStart = DateTime.Now;

for(int i=1;i<=nConNum;i++)

{

       //using 语句在离开自己的作用范围时,会自动调用被使用的对象的 Dispose

using(SqlConnection con = new SqlConnection(strConUnusePool))

       {

       con.Open();

              con.Close();

       }

}

DateTime dtEnd = DateTime.Now;

TimeSpan ts = dtEnd-dtStart;

//使用连接池技术

dtStart = DateTime.Now;

for(int i=1;i<=nConNum;i++)

{

       //using 语句在离开自己的作用范围时,会自动调用被使用的对象的 Dispose

using(SqlConnection con = new SqlConnection(strConusePool))

       {

              con.Open();

              con.Close();

       }

}

dtEnd = DateTime.Now;

ts = dtEnd-dtStart;

DataAdapter 优化连接

         DataAdapter Fill Update 方法在连接关闭的情况下自动打开为相关命令属性指定的连接。如果 Fill Update 方法打开了连接,Fill Update 将在操作完成的时候关闭它。为了获得最佳性能,仅在需要时将与数据库的连接保持为打开。同时,减少打开和关闭多操作连接的次数。

         如果只执行单个的 Fill Update 方法调用,建议允许 Fill Update 方法隐式打开和关闭连接。如果对 Fill / Update 调用有很多,建议显式打开连接,调用 Fill / Update,然后显式关闭连接。

始终关闭 Connection DataReader

         完成对 Connection DataReader 对象的使用后,总是显式地关闭它们。尽管垃圾回收最终会清除对象并因此释放连接和其他托管资源,但垃圾回收仅在需要时执行。

C# 中使用 “Using” 语句

         using 语句在离开自己的作用范围时,会自动调用被使用的对象的 Dispose

连接异常

         DataException :表示使用 ADO.NET 组件发生错误时引发的异常

         DBConcurrencyException :在更新操作过程中受影响的行数等于零时,由 DataAdapter 所引发的异常。

         SqlException : SQL Server 返回警告或错误时引发的异常。无法继承此类。

SqlException

         任何时候只要 SQL Server .NET 数据提供程序遇到服务器生成的错误,就会创建该类。SqlException 始终包含至少一个 SqlError 实例。

         严重程度等于或小于 10 的消息是信息性消息,它们指示由用户输入信息中的错误所导致的问题。严重程度 11 16 的消息是由用户生成的,可以由用户更正。严重程度 17 25 的消息指示软件或硬件错误。当发生严重程度为 1718 19 的错误时,虽然可能无法执行特定语句,但仍可以继续工作。

         当严重程度等于或小于 19 时,SqlConnection 保持打开状态。当严重度等于或大于 20 时,服务器通常会关闭 SqlConnection。但是,用户可以重新打开连接并继续操作。在这两种情况下,执行命令的方法都会生成 SqlException

[参考代码]

//容易被注入攻击的查询字符串

string strSql = "select * from ScoreTable where UserName='"+tbUserName.Text+"' and PassWord= '"+tbPassWord.Text+"'";

SqlCommand com = new SqlCommand(strSql,con);

con.Open();

SqlDataReader sdr = com.ExecuteReader();

if(sdr.Read())

       MessageBox.Show("Authenticated");

else

       MessageBox.Show("Invalid User");

sdr.Close();

con.Close();

//相对不容易被注入攻击的查询字符串

string strSql = "select * from ScoreTable where UserName='"+tbUserName.Text.Replace("'", "''")

              +"' and PassWord= '"+tbPassWord.Text.Replace("'", "''")+"'";

//在第一种情况下,如果tbUserName输入用户名”+” ‘--”就容易把密码的检查项注释掉,而在知道用到用户名而不知道密码的情况下进入系统;或者直接tbUserName=”’ OR !=1 ‘--”

这样就在不需要知道用户名和密码的情况下进入系统。为什么会这样呢?这是因为系统没有对查询字符串进行检查,导致进行SQL查询时执行了编辑框的内容,所以容易形成注入攻击。第二种情况把T-SQL 中的注释标志” ' ”进行替换,防止了注入攻击。当然还有其他情况,就需要下功夫研究了。

使用命令最佳实践

Command对象的使用

 

方法

描述

Cancel

取消数据命令的执行

CreateParameter

创建一个新的参数

ExecuteNonQuery

执行命令并返回受影响的行数

ExecuteReader

执行命令并返回生成的DataReader

ExecuteScalar

执行查询并返回结果集中的第一行的第一列

ExecuteXmlReader

执行命令并返回生成的XMLReader

Prepare

在数据源上创建一个准备好的命令版本

ResetCommandTimeOut

CommandTimeOut属性重置为默认值

DataReader

         当数据命令返回结果集时,用DataReader 来检索数据

         DataReader对象返回一个来自数据命令的只读的、只能向前的数据流

         内存中每次仅有一个数据行,因此开销很少

ExecuteScalar ExecuteNonQuery

         如果想返回像 Count(*)Sum(Price) Avg(Quantity) 的结果那样的单值,可以使用 Command.ExecuteScalar

         因为单独一步就能完成,所以 ExecuteScalar 不仅简化了代码,还提高了性能;要是使用 DataReader 就需要两步才能完成(即,ExecuteReader + 取值)。

         使用不返回行的 SQL 语句时,例如修改数据(例如INSERTUPDATE DELETE)或仅返回输出参数或返回值,请使用 ExecuteNonQuery。这避免了用于创建空 DataReader 的任何不必要处理。

使用 SqlCommand 的最佳实践

         存储过程是SQLServer数据库的一个重要特色

         存储过程执行效率比SQL文本命令要高的多

         提高了程序的复用性

         存储过程中可以使用变量和条件

         可以在存储过程中使用参数

         如果调用存储过程,将 SqlCommand CommandType 属性指定为 StoredProcedure CommandType。这样通过将该命令显式标识为存储过程,就不需要在执行之前分析命令。

使用 Prepare 方法

         对于重复作用于数据源的参数化命令,Command.Prepare 方法能提高性能。

         对于一些数据源(例如 SQL Server 2000),命令是隐式优化的,不必调用 Prepare

         对于其他(例如 SQL Server 7.0)数据源,Prepare 会比较有效。

测试 Null

         如果表(在数据库中)中的列允许为空,就不能测试参数值是否等于空。

         SELECT * FROM Customers WHERE ((LastName = @LastName)

OR (LastName IS NULL AND @LastName IS NULL))

Null 作为参数值传递

         对数据库的命令中,当把空值作为参数值发送时,不能使用 nullVisual Basic .NET 中为 Nothing)。而需要使用 DBNull.Value

事务处理

ADO.NET 的事务模型已经更改。

w        ADO 中,当调用 StartTransaction 时,调用之后的任何更新操作都被视为是事务的一部分。

w        但是,在 ADO.NET 中,当调用 Connection.BeginTransaction 时,会返回一个 Transaction 对象,需要把它与 Command Transaction 属性联系起来。这种设计可以在一个单一连接上执行多个根事务。

w        如果未将 Command.Transaction 属性设置为一个针对相关的 Connection 而启动的 Transaction,那么 Command 就会失败并引发异常。

 

[参考代码]

//使用存储过程 CommandType指定类型

SqlCommand cmdUpdateScore = new SqlCommand("UpdateScore",con);

cmdUpdateScore.CommandType = CommandType.StoredProcedure;

cmdUpdateScore.Parameters.Add( new SqlParameter("@username", "周润发") );

cmdUpdateScore.Parameters.Add( new SqlParameter("@score", "700" ));

con.Open();

//使用事务,在ADO. NET单一连接上执行多个根事务

SqlTransaction trans = con.BeginTransaction();

cmdUpdateScore.Transaction=trans;

try

{

       cmdUpdateScore.ExecuteNonQuery();

       trans.Commit(); // No error so commit the transaction

}

catch

{

       trans.Rollback(); // Rollback the update

}

使用 DataReaderDataSetDataAdapter

执行以下操作使用 DataSet

         在结果的多个离散表之间进行导航。

         操作来自多个数据源(例如,来自多个数据库、一个 XML 文件和一个电子表格的混合数据)的数据。

         在各层之间交换数据或使用 XML Web 服务。

         重用同样的行组,以便通过缓存获得性能改善(例如排序、搜索或筛选数据)。

         每行执行大量处理。

对于下列情况,要在应用程序中使用DataReader

         不需要缓存数据。

         要处理的结果集太大,内存中放不下。

         一旦需要以只进、只读方式快速访问数据。

DataReader 的常见问题:

         在访问相关 Command 的任何输出参数之前,必须关闭 DataReader。完成读数据之后总是要关闭DataReader

         当访问列数据时,使用类型化访问器

         一个单一连接每次只能打开一个 DataReader

         默认情况下,DataReader 每次 Read 时都要把整行加载到内存。这允许在当前行内随机访问列。如果不需要这种随机访问,为了提高性能,就把 CommandBehavior.SequentialAccess 传递给 ExecuteReader 调用

         如果已经完成读取来自 DataReader 的数据,但仍然有大量挂起的未读结果,就在调用 DataReader Close 之前先调用 Command Cancel

二进制大对象 (BLOB)

 

         DataReader 检索二进制大对象 (BLOB) 时,应该把 CommandBehavior.SequentialAccess 传递给 ExecuteReader 方法调用。

         SequentialAccess DataReader 的行为设置为只加载请求的数据。然后还可以使用 GetBytes GetChars 控制每次加载多少数据。

[参考代码]

//使用CommandBehavior.CloseConnection关闭连接

Try{

       Conn.Open();

       Return (cmd.ExecuteReader(CommandBehavior.CloseConnection))

}

Catch{

       If(null!=Conn)

Conn.Close();

}

其他技巧

l         避免自动增量值冲突

    主要原因在于dataset相当于内存数据库,列可以自动增加,但是数据库中并没有自动增加。如果只是为了唯一,建议采用guid

l         检查开放式并发冲突

Dataset建议采用单线程编程



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=901500

posted @ 2006-08-18 15:49  MSDI  阅读(483)  评论(0编辑  收藏  举报