[挖掘记录] - <System.Data> - 对 Odbc 的使用以及一些发现
在 System.Data 下, 有几个提供数据驱动的命名空间, 如: System.Data.Odbc, System.Data.OleDb 等等, 今天由于实习的公司业务需要, 所以我得照着以前前辈写的代码写多一份基于 Odbc 的数据访问赋值类, 过程很简单, 基本上只要把上面的 OleDbXXX 改成 OdbcXXX 就可以了. 改完以后就进入深思啦, 因为前辈写的代码里面有用到 XXXCommandBuilder 类, 之前是没有接触过这个类的, 看了他的代码以后觉得好像很有意思, 所以就深究了一下下....
1 using (OdbcConnection connection = new OdbcConnection(connectionString)) 2 { 3 string strCmd = "SELECT * FROM TABLENAME WHRER 1=0"; 4 OdbcCommand cmd = new OdbcCommand(strCmd, connection); 5 OdbcDataAdapter da = new OdbcDataAdapter(cmd); 6 OdbcCommandBuilder cb = new OdbcCommandBuilder(da); 7 8 DataTable dt = new DataTable(); 9 //-- 在此处对 dt 中的行进行填充 -- 10 11 da.Update(dt); 12 }
这段代码的目的是将一些数据插入数据库中, 但明显在代码中我们找不到包含 Update 的 Sql 语句.
我的理解是, OdbcCommandBuilder 的功劳, 虽然 strCmd 查询语句返回的是一个空表, 但它仍然返回了一些表中的列的信息, 这些信息会被记录在 OdbcDataAdapter 中, 然后在建立 OdbcCommandBuilder 实例时, 我们把 OdbcDataAdapter 做为构造函数的参数传了过去, 这样就在 OdbcDataAdapter 和 OdbcCommandBuilder 中建立了联系, 我们可以理解为, 当 OdbcDataAdapter 需要什么 Sql 操作语句, 而自身又没有的时候, 它可以通过 OdbcCommandBuilder 来获得. 这就是我对 OdbcCommandBuilder 的一些思考.
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string strCon = "Driver={SQL Server}; SERVER=(local); DATABASE=DataBaseName;Integrated Security=SSPI;Trusted_Connection=yes"; 6 using (OdbcConnection con = new OdbcConnection(strCon)) 7 { 8 OdbcCommand cmd = new OdbcCommand("SELECT * FROM [Test];", con); 9 OdbcDataAdapter da = new OdbcDataAdapter(cmd); 10 OdbcCommandBuilder cb = new OdbcCommandBuilder(da); 11 DataSet ds = new DataSet(); 12 da.Fill(ds); 13 14 Console.WriteLine("Before: "); 15 ShowTable(ds.Tables[0]); 16 17 Console.WriteLine("Deleting Rows[0]:"); 18 DataRow dr = ds.Tables[0].Rows[0]; 19 dr.Delete(); 20 Console.WriteLine("Rows Count:" + ds.Tables[0].Rows.Count); 21 Console.WriteLine("Updating:"); 22 da.Update(ds); 23 24 Console.WriteLine("After Deleting:"); 25 ShowTable(ds.Tables[0]); 26 27 Console.WriteLine("Adding;"); 28 ds.Tables[0].Rows.Add(dr); 29 Console.WriteLine(ds.Tables[0].Rows.Count); 30 da.Update(ds); 31 32 Console.WriteLine("After Adding:"); 33 ShowTable(ds.Tables[0]); 34 } 35 } 36 37 private static void ShowTable(DataTable dt) 38 { 39 foreach (DataRow dr in dt.Rows) 40 { 41 foreach (DataColumn dc in dt.Columns) 42 { 43 Console.Write(dr[dc.ColumnName].ToString() + " "); 44 } 45 Console.WriteLine(); 46 } 47 } 48 }
首先, 写这段代码的目的纯粹是看看 OdbcCommandBuilder 怎么用而已, 试试手, 但没想到一调试之下, 发现的东西还不少;
1. 原本想试试在一个表中手工插入两条完全一样的数据, 然后看看执行 Delete 操作的时候, OdbcDataAdapter 是同步到数据库那边去的结果会是怎么样, 会不会把两行都删除了. 但一运行到更新那一步(22)就报错了... 说是 DataAdapter 中没有列的具体信息, 所以我想应该是没有主键, 无法索引到具体的行的问题吧. 所以把表内相同的行删除掉, 然后设一个主键以后就可以调试了; -> 因为 Insert 操作不用索引到具体的行, 故对没有主键的表执行 Insert 操作是可以调试通过的
2. 一开始我看的时候还觉得很奇怪, DataAdapter 里面有 SelectCommand, DeleteCommand, UpdateCommand, InsertCommand 四个属性对应四种基本 Sql 操作, 但是却没有 Select, Delete, Insert 方法, 只有一个 Update 方法, 那具体四种操作要怎么体现呢? 乱打乱撞下, 我看到了 DataRow 对象下有一个叫 RowState 的只读属性, 它的值是一个 DataRowState 枚举: Added, Deleted, Detached, Modified, Unchanged. 具体每个属性代表什么意思可以通过 Visual Studio 的智能提示查看代码说明. 通过这个发现, 我们就可以知道 OdbcDataAdapter 的 Update 方法到底是怎么操作的了: 首先, 无论一个 DataTable 是包含在从数据库中获取的 DataSet 中, 还是直接在现有代码上创建的, 只要一个 DataRow 添加到其 Rows 集合中, 则该 DataRow 的 RowState 属性将被改为 Added (原本应该是 Detached), 该值对应 Insert 操作; 而 Deleted 对应 Deleted 操作, Modified 则对应 Update, 而 Unchaned 则当然是什么都不做了;
3. 对 DataRow 对象的 Delete() 方法和 DataTable.Rows.Remove() 方法必须区分开来, Delete 方法说白了也就只是把 DataRow 的 RowState 属性设为 Delete 而已, 而如果一个 DataRow 的 RowState 属性为值 Delete, 则其中各个字段的值将变得无法读取 (不知是被清空了还是设置了访问控制), 但主要, 这个行仍是在 DataTable.Rows 集合中的, 故你执行 Delete() 前后读取到的 DataTable.Rows.Count 属性的值都是一样的; 而 Remove 操作则单纯的是将 DataRow 从集合中移除, 这种情况反映到数据库后, 将不对这个 DataRow 对应的行进行任何操作; 到此, 可以看到, 两者的区别还是很大的;
4. 在 27 - 30 这 4 行代码中, 本来只是单纯的一个偷懒操作, 想让他在测试结束的时候把删除的行又添加到数据库中去, 不然把表里面的数据删完了我还得手动去加, 结果运行到这里居然报错了, 完全出乎我的意料. 不过调试了以后到也马上明白了, 因为在之前的操作中, 我们调用了 dr 的 Delete() 方法, 故 dr 的 RowState 属性是 Delete, 由此导致, 其中各个字段变为无法读取, 可以理解为空, 故插入会失败.