1. 异步执行命令
在ADO.NET 2.0版本之前,执行Command对象命令时,需要等待命令完成才能执行其他操作。比如,执行ExcuteNonQuery()方法,应用程序将会保持阻塞,直到数据操作成功完成或者异常终止以及连接超时。在ADO.NET 2.0版本引入异步执行特性,显然,ADO.NET更稳健,更完美了。
异步执行的根本思想是,在执行命令操作时,无需等待命令操作完成,可以并发的处理其他操作。ADO.NET提供了丰富的方法来处理异步操作,BeginExecuteNonQuery和EndExcuteNonQuery就是一对典型的为异步操作服务的方法。BeginExecuteNonQuery方法返回System.IAsyncResult接口对象。我们可以根据IAsyncResult的IsCompleted属性来轮询(检测)命令是否执行完成。还是来看一个简单的实例把!这个实例采用了《你必须知道的ADO.NET(六) 谈谈Comand对象与数据检索》中的数据库和数据表。在这个实例中,我们将在tb_SelCustomer中插入500行数据,并计算执行时间。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data;//必须引入 6 using System.Data.SqlClient;//必须引入 7 8 namespace Command 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder(); 15 connStr.DataSource = @".\SQLEXPRESS"; 16 connStr.IntegratedSecurity = true; 17 connStr.InitialCatalog = "db_MyDemo"; 18 connStr.AsynchronousProcessing = true;//必须显示说明异步操作 19 20 StringBuilder strSQL = new StringBuilder(); 21 22 //插入100个测试客户 23 for (int i = 1; i <= 500; ++i) 24 { 25 strSQL.Append("insert into tb_SelCustomer "); 26 strSQL.Append("values('"); 27 string name = "测试客户" + i.ToString(); 28 strSQL.Append(name); 29 strSQL.Append("','0','0','13822223333','liuhaorain@163.com','广东省深圳市宝安区',12.234556,34.222234,'422900','备注信息'); "); 30 } 31 32 using (SqlConnection conn = new SqlConnection(connStr.ConnectionString)) 33 { 34 conn.Open(); 35 SqlCommand cmd = new SqlCommand(strSQL.ToString(), conn); 36 37 IAsyncResult pending = cmd.BeginExecuteNonQuery();//开始执行异步操作 38 double time = 0; 39 40 //检查异步处理状态 41 while (pending.IsCompleted == false) 42 { 43 System.Threading.Thread.Sleep(1); 44 time++; 45 Console.WriteLine("{0}s", time * 0.001); 46 } 47 48 if (pending.IsCompleted == true) 49 { 50 Console.WriteLine("Data is inserted completely...\nTotal coast {0}s", time * 0.001); 51 } 52 53 cmd.EndExecuteNonQuery(pending);//结束异步操作 54 } 55 56 Console.Read(); 57 } 58 } 59 }
处理结果如下:
2. 请使用参数化查询
在ADO.NET中,查询语句是以字符串的形式传递给外部数据库服务器的。这些字符串不仅包含了基本命令关键字,操作符,还包含了限制查询的数值。与其他编程语言不同,.NET是基于强类型来管理查询字符串数据的。通过提供类型检查和验证,命令对象可使用参数来将值传递给 SQL 语句或存储过程。 与命令文本不同,参数输入被视为文本值,而不是可执行代码。 这样可帮助抵御“SQL 注入”攻击,这种攻击的攻击者会将命令插入 SQL 语句,从而危及服务器的安全。参数化命令还可提高查询执行性能,因为它们可帮助数据库服务器将传入命令与适当的缓存查询计划进行准确匹配。
对于不同的数据源来说,Parameter对象不同,但都派生自DbParameter对象。下表列举了不同数据源对应的Parameter对象。
数据提供程序 | 对应Paramter对象 | 命名空间 |
SQLServer 数据源 | 使用SqlParamter对象 | System.Data.SqlClient.SqlParameter |
Ole DB 数据源 | 使用OleDbParameter对象 | System.Data.OleDb.OleDbParameter |
ODBC 数据源 | 使用OdbcParamter对象 | System.Data.Odbc.OdbcParameter |
Oracle数据源 | 使用OracleParameter对象 | System.Data.OracleClient.OracleParameter |
Paramter对象的属性很多,其中常见而且非常重要的主要有以下几个:
DbType: 获取或设置参数的数据类型。
Direction: 获取或设置一个值,该值指示参数是否只可输入、只可输出、双向还是存储过程返回值参数。
IsNullable: 获取或设置一个值,该值指示参数是否可以为空。
ParamteterName: 获取或设置DbParamter的名称。
Size: 获取或设置列中数据的最大大小。
Value: 获取或设置该参数的值。
以SQL Server为例,SqlCommand对象包含一个Paramters集合,Paramters集合中包含了所有所需的SqlParamter对象。当执行命令时,ADO.NET同时将SQL文本,占位符和参数集合传递给数据库。
提示: 对于不同的数据源来说,占位符不同。SQLServer数据源用@parametername格式来命名参数,OleDb以及Odbc数据源均用问号(?)来标识参数位置,而Oracle则以:parmname格式使用命名参数。
下面我们看一个实例,修改 测试顾客1 的基本信息。修改的基本信息如下:
电话号码(Phone):18665691100
电子邮箱(Email):test@163.com
地址(ContactAddress):中国深圳市南山区
代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data; 6 using System.Data.SqlClient; 7 8 9 namespace Command2 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //构造连接字符串 16 SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder(); 17 connStr.DataSource = @".\SQLEXPRESS"; 18 connStr.IntegratedSecurity = true; 19 connStr.InitialCatalog = "db_MyDemo"; 20 21 //拼接SQL语句 22 StringBuilder strSQL = new StringBuilder(); 23 strSQL.Append("Update tb_SelCustomer Set "); 24 strSQL.Append("Phone = @Phone,"); 25 strSQL.Append("Email = @Email,"); 26 strSQL.Append("ContactAddress = @Address "); 27 strSQL.Append("where Name = @Name"); 28 29 using (SqlConnection conn = new SqlConnection(connStr.ConnectionString)) 30 { 31 SqlCommand cmd = new SqlCommand(strSQL.ToString(), conn); 32 33 //构造Parameter对象 34 SqlParameter para1 = new SqlParameter("@Phone", SqlDbType.VarChar, 12); 35 SqlParameter para2 = new SqlParameter("@Email",SqlDbType.VarChar,50); 36 SqlParameter para3 = new SqlParameter("@Address",SqlDbType.VarChar,200); 37 SqlParameter para4 = new SqlParameter("@Name",SqlDbType.VarChar,20); 38 39 //给Parater对象赋值 40 para1.Value = "18665691100"; 41 para2.Value = "test@163.com"; 42 para3.Value = "中国深圳市南山区"; 43 para4.Value = "测试客户1"; 44 45 //添加到Parameters集合中 46 cmd.Parameters.Add(para1); 47 cmd.Parameters.Add(para2); 48 cmd.Parameters.Add(para3); 49 cmd.Parameters.Add(para4); 50 51 try 52 { 53 conn.Open(); 54 cmd.ExecuteNonQuery(); 55 Console.WriteLine("Update Success..."); 56 } 57 catch(Exception ex) 58 { 59 Console.WriteLine("{0}",ex.Message); 60 } 61 } 62 63 Console.Read(); 64 } 65 } 66 }
看了上面的代码不知大家有何感想,是否觉得太多繁琐呢?的确,我们可以用更简洁的方法来实现。具体方法是,我们可以先构造Parameter对象数组,然后遍历添加到Command对象的Paramters集合中。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data; 6 using System.Data.SqlClient; 7 8 9 namespace Command2 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //构造连接字符串 16 SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder(); 17 connStr.DataSource = @".\SQLEXPRESS"; 18 connStr.IntegratedSecurity = true; 19 connStr.InitialCatalog = "db_MyDemo"; 20 21 //拼接SQL语句 22 StringBuilder strSQL = new StringBuilder(); 23 strSQL.Append("Update tb_SelCustomer Set "); 24 strSQL.Append("Phone = @Phone,"); 25 strSQL.Append("Email = @Email,"); 26 strSQL.Append("ContactAddress = @Address "); 27 strSQL.Append("where Name = @Name"); 28 29 using (SqlConnection conn = new SqlConnection(connStr.ConnectionString)) 30 { 31 SqlCommand cmd = new SqlCommand(strSQL.ToString(), conn); 32 33 //构造Parameter对象 34 SqlParameter[] paras = new SqlParameter[]{ 35 new SqlParameter("@Phone", SqlDbType.VarChar, 12), 36 new SqlParameter("@Email", SqlDbType.VarChar, 50), 37 new SqlParameter("@Address", SqlDbType.VarChar, 200), 38 new SqlParameter("@Name", SqlDbType.VarChar, 20) 39 }; 40 41 //给Parater对象赋值 42 paras[0].Value = "18665691100"; 43 paras[1].Value = "test@163.com"; 44 paras[2].Value = "中国深圳市南山区"; 45 paras[3].Value = "测试客户1"; 46 47 //遍历添加到Parameters集合中 48 foreach (var item in paras) 49 { 50 cmd.Parameters.Add(item); 51 } 52 53 try 54 { 55 conn.Open(); 56 cmd.ExecuteNonQuery(); 57 Console.WriteLine("Update Success..."); 58 } 59 catch (Exception ex) 60 { 61 Console.WriteLine("{0}", ex.Message); 62 } 63 } 64 65 Console.Read(); 66 } 67 } 68 }
上面两种写法,结果完全相同。查询数据库,我们可以得到以下结果:
3. 如何获取插入行的ID?
很多时候,我们需要知道插入行的ID是多少,以方便我们进行利用插入行的ID进行其他操作,比如在页面上的展示等等。当然实现的方法有很多种,比如利用C#的out修饰符修饰参数,我更倾向于用SQL Server数据库原生的OUTPUT关键字。OUTPUT关键字返回INSERT操作的一个字段(一般是主键ID)。因此我们只要结合OUTPUT关键字以及ExecuteScalar方法,就很容易得到插入行的主键。还是看一个简单的实例把!我们在tb_SelCustomer中插入一个新的顾客,并返回这个顾客的ID。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data; 6 using System.Data.SqlClient; 7 8 9 namespace Command2 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //构造连接字符串 16 SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder(); 17 connStr.DataSource = @".\SQLEXPRESS"; 18 connStr.IntegratedSecurity = true; 19 connStr.InitialCatalog = "db_MyDemo"; 20 21 //拼接SQL语句 22 StringBuilder strSQL = new StringBuilder(); 23 strSQL.Append("insert tb_SelCustomer(Name) "); 24 strSQL.Append("OUTPUT inserted.ID values(@Name)"); 25 26 using (SqlConnection conn = new SqlConnection(connStr.ConnectionString)) 27 { 28 SqlCommand cmd = new SqlCommand(strSQL.ToString(), conn); 29 30 SqlParameter para = new SqlParameter("@Name", SqlDbType.VarChar, 20); 31 para.Value = "Kemi"; 32 cmd.Parameters.Add(para); 33 34 try 35 { 36 conn.Open(); 37 int insertedID = (int)cmd.ExecuteScalar();//获取单个值 38 39 Console.WriteLine("Inserted ID:{0}", insertedID); 40 } 41 catch (Exception ex) 42 { 43 Console.WriteLine("{0}", ex.Message); 44 } 45 } 46 47 Console.Read(); 48 } 49 } 50 }
运行结果如下:
4. 总结
简言之,Command对象的核心作用是执行命令。在执行命令过程中,面临的情况是十分复杂的。尽管如此,Command对象拥有优越的人力资源(属性和方法),来应对一切可能发生的事。可以说,Command对象的稳定发挥,为ADO.NET打下了扎实的根基。到目前为止,我们基本上了解ADO.NET DataProvider组件所有的内容。因此,后面我将重点讲述ADO.NET的心脏----DataSet以及如何将数据源本地化。另外,我非常期待能得到您的推荐和关注。