代码改变世界

数据访问技术系列课程 笔记(5) ADO.NET 非连接方式进行数据访问

2011-07-09 19:00  lujiao_cs  阅读(493)  评论(0编辑  收藏  举报

 

一)配置DataAdapter 以检索信息:Select

DataAdapter的作用:

  填充数据集合,将数据的更改提交到数据源。可以使用多个适配器去填充,也可以将不同的数据源的数据集成到数据集中(数据集成),然后提供给前端程序使用。

 

<图 1>

1)数据适配器是数据集与数据源交互的桥梁

  a) 使相当于数据源本地拷贝的数据集可以与数据源进行交互

2)为数据库提供的主要两种数据适配器

  a) SqlDataAdapter:不经过OLEDB层(标准访问层)直接与SQLServer交互,速度较OleDbDataAdapter

  b) OleDbDataAdapter:适用于任何可以用OLEDB数据提供者访问的数据源

 

XxxDataAdapter对象模型

  DataAdapter是通过一系列Command实现的,非连接方式在概念上不一样,实际也是通过连接方式下的一些命令实现的。

<图 2>

1)Command对象

  a) 通过数据适配器来读取数据源信息的命令对象,并将其保存在数据适配器的SelectCommand属性中。

  b) 通过数据适配器可以将数据集中的改变提交到数据源的Command对象中,并保存在数据适配器的InsertCommandUpdateCommandDeleteCommand属性中。

2DataTableMapping(数据表映射)集合

  DataTableMapping集合保存了数据集中的表、字段与数据库中的表、字段的映射关系。

3)数据适配器的属性:

  SelectCommand  从数据源中读取数据

  InsertCommand  将数据源有数据集插入数据源

  UpdateCommand 将数据集中更新的行写回数据源

  DeleteCommand  在数据源中删除数据

4)数据适配器的方法

  a) Fill() :使用有SelectCommand属性指定的Select语句从数据源中读取/更新数据到数据集。

  b) Update() :对数据集DateTable对象中特定的行调用执行InsertDeleteUpdate操作的对应的命令对象。执行的时候,需要判断当前数据集里面哪些数据发生变化,需要在底层数据源做相应的变化。

5)创建使用新Select语句的DataAdapter

  a) 创建一个执行select语句的数据适配器:对非连接方式应用以只读方式访问数据

  b) 在定义数据适配器时必须指定:一个用于查询的select语句,一个新的或者已经存在的数据库连接

例如:

DataSet ds = new DataSet();

SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users","server=(local);database=DemoDB;Integrated Security=true;");

da.Fill(ds);  //除了SelectCommand,其它都是空的。

this.dataGridView1.DataSource = ds.Tables[0];

 

6)创建使用现有存储过程的DataAdapter

  a) 可以编程创建一个执行存储过程:为SelectCommand指定一个存储过程,如果需要可以为InsertCommandUpdateCommandDeleteCommand执行存储过程

  b) 必须指定:一个新的或者已经存在的数据库连接,调用的存储过程

例如:

 DataSet ds = new DataSet();

 SqlDataAdapter dAdapter = new SqlDataAdapter("GetAllUsers", "server=(local);database=DemoDB;Integrated Security=true;");

 dAdapter .SelectCommand.CommandType = CommandType.StoredProcedure;

 dAdapter .Fill(ds);

 this.dataGridView1.DataSource = ds.Tables[0];

 

二)使用DataAdapter 填充DataSetFill

1)可以使用数据适配器来填充数据集:调用数据适配器的Fill()方法(实际上是执行SelectCommand)。

2)Fill()方法执行SelectCommand:使用查询结果的内容和结构填充数据集中的数据表

3)可以通过下列方法提高性能

  :如果填充的是非类型化的数据集(结构没有预先指定好),结构随着填充创建好。如果填充的是类型化的DataSet,它的架构都指定了,对于一些约束,比如:唯一约束、主外键的约束。DataSet会强制检查这些约束,我们可以设置强制检查为false

  :DataSet.EnforceConstraints = false

  :对DataTable对象调用BeginLoadData()方法(开始加载的时候,将数据表数据列去除,而不受约束限制,最后调用EndLoadData()方法,建立约束。

 

高效的填充DataSet(类型化的)

1)在填充一个数据集前显示的定义数据结构

  :数据表、数据列以及数据关联在数据被载入前已经确定

  :是数据可以被高效的载入

2)如何显式地定义一个数据集的数据机构

  :创建一个类型化的数据集类

    dsCustomers.Customers.BeginLoadData();

    daCustomers.Fill(dCustomers.Customers);

    dsCustomers.Customers.EndLoadData();

    DataGrid1.DataSource = dsCustomers.Customers.DefalutView;

  :或者以编程的方式创建数据表、数据列和数据关联等对象

例如:dtUsers是一个类型化的数据集

1)Enforce Constrains: 

  SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", "server=(local);database=DemoDB;Integrated Security=true;");

  da.Fill(dtUsers);

2)Not Enforce Constrains:

  this.dsUsers.EnforceConstraints = false; //不遵循约束规则

  This.dtUsers.BeginLoadData(); //加载数据时关闭通知、索引维护和约束

  SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", "server=(local);database=DemoDB;Integrated Security=true;");

  da.Fill(dtUsers);

  this.dtUsers.EndLoadData(); //加载数据后打开通知、索引维护和约束

 

DataSet指定附加约束(非类型化的)

1)可以再数据结构未知的情况下填充数据集

  :数据集的数据结构在设计阶段未知

  :在运行时根据得到的数据来确定数据集的数据结构

  :可以在运行时通过数据适配器控制如何创建并生成数据集的数据结构

2)使用MissingSchemaAction属性控制数据结构生成

3)调用FillSchema()方法建立一个新的数据集的数据结构

例如:

1)MissingSchemaAction

  Add:数据每次都是累加

  AddWithKey:数据根据主键加载

  Error:没有Scheme 会报错

  Ignore:忽略没有架构的数据集(非类型化报错)

  SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", "server=(local);database=DemoDB;Integrated Security=true;");

  da.MissingSchemaAction = (MissingSchemaAction)

  Enum.Parse(typeof(MissingSchemaAction),((ToolStripMenuItem)sender).Text);

  da.Fill(dataSet1);

  this.dataGridView1.DataSource = dataSet1.Tables[0];

2FillSchema

  SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", "server=(local);database=DemoDB;Integrated Security=true;");

  da.FillSchema(this.dataSet1, SchemaType.Source);

  this.dataGridView1.DataSource = dataSet1.Tables[0];

 

使用多个DataAdapter填充一个DataSet

1)可以使用多个数据适配器填充一个数据集:每个数据适配器填充数据集中一个独立的表

2)为每个数据适配器调用Fill()方法:在数据集中指定填充哪一个表

例如:

  SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users",  "server=(local);database=DemoDB;Integrated Security=true;");

  da.Fill(dataSet1,"Users");

  da = new SqlDataAdapter("SELECT * FROM Groups",  "server=(local);database=DemoDB;Integrated Security=true;");

  da.Fill(dataSet1, "Groups");

  this.dataGridView1.DataSource = dataSet1.Tables[0];

 

三)配置DataAdapter 更新后台数据源


DataSet跟踪更改的方式

1)每个数据行对象都有一个RowState属性

  a) 标识数据集中每一行数据的状态

  b) 状态的类型

    DataRowState.Added     该行已经插入到数据集(还未提交修改 AcceptChanges())

· DataRowState.Deleted    该行已经从数据集中删除

· DataRowState.Detached   该行已经创建,但未增加到数据集中的DataRowCollection

· DataRowState.Modified   该行已经更改

· DataRowState.Unchanged 该行没有发生任何变化

2)每个数据集都对每一行数据维护两份拷贝

:当前版本 DataRowVersion.Current

 If(row.RowState == DateRowState.Added)

 Row["FieldName",DataRowVersion.Current]

:原始版本 DataRowVersion.Original

 If(row.RowState == DateRowState.Deleted)

 Row["FieldName",DataRowVersion.Original]

注意:

1)如果row.RowState == DataRowState.Deleted

那么访问:Row["FieldName",DataRowVersion.Current] 出现异常。“值不存在”

2)如果dt.Rows.RemoveAt(0)

那么第0行将被完全删除,没有row.RowState == DataRowState.Deleted这回事了。

3)如果赋值:DataView dv = dt.DefaultView;

那么如果修改dv 的某些值,dt 的值以及RowState 也会变化。

4)dv 取出的数据行不包括 RowStateDataRowState.Deleted

5)如果 第行:row.RowState == DataRowState.Added

那么dv.Delete(0) ,则dt.Rows.Count 会减1,反之不会。

 

数据更新命令

1)一个SqlDataAdapterOleDbDataAdapter对象都有一些命令对象可以用来更改数据源的数据 

    – InsertCommand 

    – UpdateCommand 

    – DeleteCommand 

2) 语法:对SqqlOleDb的数据数据适配器以及各个命令各个命令对象完全相同 

–  ppublic SqqlCommand InsertCommand {{get;set;};} 

 

使用CommandBuilder生成命令

1)InsertCommand

在数据源处为表中所有RowStateAdded的行插入一行。插入所有可更新列的值(但是不包括标识、表达式或时间戳等列)。

2UpdateCommand

在数据源处更新表中所有RowStateModified的行。更新所有列的值,不可更新的列除外,例如标识列或表达式列?。

更新符合以下条件的所有行:数据源中的列值匹配行的主键列值,并且数据源中剩余列匹配行的原始值。

3DeleteCommand

在数据源处删除表中所有RowStateDeleted的行。

删除符合以下条件的所有行:数据源中的列值匹配行的主键列值,并且数据源中剩余列匹配行的原始值。

 

例:

1) 查看RowState

DataTable rv = new DataTable();

    rv.Columns.Add("Row ID");

    rv.Columns.Add("Row Version");

    rv.Columns.Add("Original Name");

    rv.Columns.Add("New Name");

    foreach (DataRow dr in dt.Rows)

    {

    switch (dr.RowState)

    {

    case DataRowState.Added:

    rv.Rows.Add(dr[0, DataRowVersion.Current].ToString(), 

    dr.RowState.ToString(), "", dr[1, DataRowVersion.Current].ToString());

    break;

    case DataRowState.Deleted:

    rv.Rows.Add(dr[0, DataRowVersion.Original].ToString(), 

    dr.RowState.ToString(), dr[1, DataRowVersion.Original].ToString(), "");

    break;

    case DataRowState.Modified:

    rv.Rows.Add(dr[0, DataRowVersion.Current].ToString(), 

    dr.RowState.ToString(), dr[1, DataRowVersion.Original].ToString(),

     dr[1, DataRowVersion.Current].ToString());

    break;

    case DataRowState.Unchanged:

    rv.Rows.Add(dr[0, DataRowVersion.Current].ToString(), 

    dr.RowState.ToString(), dr[1, DataRowVersion.Original].ToString(),

     dr[1, DataRowVersion.Current].ToString());

    break;

    }

}

2)更新

SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", "server=(local);database=DemoDB;Integrated Security=true;");

SqlCommandBuilder builder = new SqlCommandBuilder(da); 

// 以下的命令都是由SqlCommandBuilder 自动生成的。

da.InsertCommand = builder.GetInsertCommand(true);

da.DeleteCommand = builder.GetDeleteCommand(true);

da.UpdateCommand = builder.GetUpdateCommand(true);

//da.AcceptChangesDuringUpdate = false; 加上这句,修改就不会更新

da.Update(dtUsers);

3)查看某一种状态的行

 this.dataGridView1.DataSource = dtUsers.GetChanges(DataRowState.Added/DataRowState.Deleted/......);

 

四)将数据更改保存到数据源

使用DataSet对象的GetChanges方法的时机 

1)当需需要将数将数据更改传给传给由另一个个对象使象使用的的另一个类的时个类的时 

候调用GetChanges()方法

If dsCustomersds.HasChanges(DataRowStateDataRowState.ModifiedModified)  Then 

       Dim dsTemp As DataSet 

       dsTemp = dsCustomers.GetChanges(DataRowState.Modified) 

       DataGrid1.DataSource = dsTemp.Tables(0).DefaultView 

    End If 

2)使用使用GetChanges()方法得到包含该数据集中所有数据更改的数据集的拷贝 

a)从数据被载入开始从数据被载入开始

b)从AcceptChanges()方法最后一次被调用开始 

 

将更改合并到一个DataSet对象

1)使用Merge()方法合并两个数据集:一个原始数据集以及一个仅包含对原始数据集更改的数据集 

       aDataSeta.Merge(anotherDataSet) 

2)被合并的两个数据集要有相同的数据结构

 

使用DataSe更新数据源

1)数据适配器的Update()方法对指定的数据表中被更新过的每一行调用适当的SQL语句 

    – INSERT 

    – UPDATE 

    – DELETE 

2)代码示例

       aDataAdapter.Updatea(aDataSeta, aDataTable)

 

DataSet接受数据更改的方式 

1) 数据集的AcceptChanges()方法提交自数据被载入或自此次调用起所有该数据集的数据更改(掉用此方法后,DataRowState 的值全部变为:DataRowState.Unchanged)

2)可以对整个数据集调用AcceptChanges()方法,或者对一个DataTable 对象的一个数据行对象调用

 

五)冲突处理:并发问题(开放式并发或乐观并发)

发生冲突 

1)非连接环境使用了开放式并发机制 

    –  在一步数据操作完成后数据库锁立即被释放 

    –  非连接环境使用开放式并发机制保证其他资源对数据库的同步访问 

    –  保守式并发机制在整个数据操作过程中保持数据库的锁 

2)在更新数据库时会产生数据冲突在更新数据库时会产生数据冲突 

    –  另一个应用或服务可能已经更改了数据 

3)例如 

    –  删除已经不存在的行 

    –  更新已经被更新的列 

 

检测冲突 

1)数据适配器配置向导可以产生用来监测冲突的SQL语句

2)当更新数据库时

– 数据更新命令比较数据库中的当前数据与原始数据值

– 任何不同都会抛出一个冲突

 

解决冲突 

1)使用HasErrors属性来测试错属性来测试错误 

     – DataSet.HasErrors 

     – DataTable.HasErrors 

     – DataRow.HasErrors 

2)选择下列一种策略解决冲突 

    –  用数据集中的值覆盖曾经有过的数据操作用数据集中的值覆盖曾经有过的数据操作,适用于管理员系统用来强制将数据覆盖数据源中的数据 

–  保持数据集中冲突行以便后续重新更新数据库 

          •  将冲突的数据保存在数据集中以便重试 

          •  “使用开放式并发”选项的默认策略 

3)拒绝冲突的行并在数据集中回滚到初始值

    – 拒绝在本地数据集中冲突的数据,将数据回滚到从数据库中加载得到的初始值

– 对冲突的数据集对冲突的数据集、数据表、数据行调用RejectChanges()方法 

4)拒绝冲突的行并从数据库得到最近的数据

    – 调用数据集的Clear()方法,重新从数据库中加载数据

 

例如:

SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Users", 

"server=(local);database=DemoDB;Integrated Security=true;");

SqlCommandBuilder builder = new SqlCommandBuilder(da);

da.InsertCommand = builder.GetInsertCommand(true);

da.DeleteCommand = builder.GetDeleteCommand(true);

da.UpdateCommand = builder.GetUpdateCommand(true);

 

try

{

  da.Update(dtUsers);

}

catch (System.Exception ex)

{

  if (dsUsers.HasErrors)

  {

    foreach (DataTable table in dsUsers.Tables)

    {

      if (table.HasErrors)

      {

          foreach (DataRow row in table.Rows)

         {

                     if (row.HasErrors)

          {

            MessageBox.Show("Row: " + row["ID"],row.RowError);

            foreach (DataColumn col in row.GetColumnsInError())

            {

                            MessageBox.Show(col.ColumnName,"Error in this column");

            }

                          row.ClearErrors();

                         row.RejectChanges();

                     }

        }

    }

  }

}

 

一个问题:DataSet.Merge()方法的用法

服务器的缓存里面保存了一个完整的DataSet,客户端修改数据后,可以只提交少量的修改的数据,而服务器端可以将缓存的DataSet 与 changeDataSet Merge()一下,服务器端就可以更新得到最新的数据。