您的位置:首页 > 其它

ADO.NET开发最佳实践

2007-04-18 16:53 459 查看

使用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 的消息指示软件或硬件错误。当发生严重程度为 17、18 或 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 语句时,例如修改数据(例如INSERT、UPDATE 或 DELETE)或仅返回输出参数或返回值,请使用 ExecuteNonQuery。这避免了用于创建空 DataReader 的任何不必要处理。

使用 SqlCommand 的最佳实践

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

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

• 提高了程序的复用性

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

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

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

使用 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 作为参数值传递

• 对数据库的命令中,当把空值作为参数值发送时,不能使用 null(Visual 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

}

使用 DataReader、DataSet、DataAdapter

执行以下操作使用 DataSet:

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

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

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

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

• 每行执行大量处理。

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

• 不需要缓存数据。

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

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

DataReader 的常见问题:

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

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

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

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

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

二进制大对象 (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建议采用单线程编程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: