您的位置:首页 > 编程语言 > ASP

[ASP.NET学习笔记之三]ADO.NET开发最佳实践

2006-07-10 22:04 1026 查看

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 的消息指示软件或硬件错误。当发生严重程度为 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建议采用单线程编程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: