博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

使用DataSet更新记录

Posted on 2011-01-31 00:20  ☆Keep★Moving☆  阅读(1000)  评论(0编辑  收藏  举报

使用DataSet更新记录
ADO.NET的DataSet对象提供了更好的编程实现数据库更新的功能。因DataSet对象与数据库始终是不连接的,可以
添加、修改和删除DataSet对象中包含的任何脱离数据库连接的记录。在完成他修改了之后,可以通过将
DataSet对象链接到DataAdapter对象来记录传输给数据库。
首先,假定已经用Northwind 数据库中的Products表中的记录填充了DataSet,就像下面的代码片断所演示的那样:
String strSQL="SELECT ProductID,ProductName,UnitPrice FROM Products";
SqlDataAdapter adapter=new SqlDataAdatpter(strSQL,objConnection);
DataSet ds=new DataSet()
adapter.Fill(ds,ProductTable);
在上述代码的最后一行,我们将保存这些记录的DataSet中表的名称命名为ProductTable,但是通常该表的名称可以
按照您的喜好来进行指定--当然可以不必使它的名称和数据库中表的名称相同。该特性的优点是您可以很容易将
内存中表的表示数据和它在数据库中的源数据区分开。至于涉及到的应用程序,您总是处理名称为ProductTable 表,
如下代码所示:
DataTable tbl=ds.Tables[ProductTable];
如果数据库管理员(DBA)后来改变了数据库中表的名称,那么惟一需要改变的代码部分只是SELECT语句。
(事实上,绝大多数应用程序都是使用存储过程来执行数据库操作的,因此数据库管理可以修改所有引用表的存储过程,
而根本不需国修改代码。)
说明:在得到DataSet之后,应当始终尽可能快地释放到数据库连接,方法是通过调用Connection对象的Close() 方法。
1.修改DataSet中的记录
 使用手头的DataSet对象,可以修改其中包含的一条或多条记录。下面的代码片断说明了如何改变产品的价格:
DataTable tbl=ds.Tables[ProductTable];
tbl.PrimaryKey=new DataColumn[]{tbl.Columns["ProductID"]};
DataRow row=tbl.Rows.Find(1);
row.Item["UnitRrice"]=100;
   这里需要提醒大家意识到的一个关键地方是,要想修改DataSet中的记录,我们首先需要查找到该记录。这是我们以前没
有完成的事情。该过程的第一步是将数据表中的一列(或选择几个列)作为表的主键码。然后可以将主键码作为表中行的
索引来使用,以便能够快速找到需要查找的记录。
    正如在上文中所暗示的,有的时候使用单个列作为主键码。在其他的时候,可能使用几个列作为主键码。结果是将
DataTable.PrimaryKey属性指定为一起构成主键码的列集合,需要用所有相关的列来初始化主键码,就象在上述第二行代码中所显示
的那样。与之形成对照的是,如果主键码是由CategoryID列和ProductID列共同组成的,必须使用如下所示的代码来定义主键码:
tbl.PrimaryKey=new DataColumn[]{tbl.Columns["CategoryID"],tbl.Columns["ProductID"]}
继续介绍例子中下面的代码。DataRowCollection对象(由DataTable.Rows属性表示)的Find()方法接受主键码的值,并返回包含与主
键相匹配的键码的DataRow.如果主键码是由多个列组成,需要传入一组数值。例如,如果主键码是由CategoryID列和ProductID列组成的,
如下所示的代码将查找CategoryID值为1并且ProductID数值为3的记录行。
Object KeyValues(1);
KeyValues(0)=1;
KeyValues(1)=3;
DataRow row=tbl.Rows.Find(KeyValues);
查到记录之后,就可以修改记录中它表示的任何字段,只需为字段指定一个新值即可。在前面的例子中,通过将UnitPrice字段的值指定为
100来修改该字段。可以按照需要对该记录进行多处修改。也可以通过对记录进行搜索和执行与上述相同的操作步骤来修改更多的记录。
记住,这里进行的修改仅仅影响的是DataSet--数据库在这时还没有被更新
2.更新数据库中的记录
  要想您在DataSet对象中进行的修改放到数据库中,首先必须要连接到数据库中,然后使用(与您用来检索数据相同的)DataAdapter对象来
用新的数据信息更新数据库:
objConnection.Open()
adapter.Update(ds,ProductTable);
objConnection.Close();
正像在上述代码在所看到的,在将记录插入前一章创建的数据库时,通过对数据适配器的Update()方法的调用巧妙地完成了新数据信息传入数据库的
任务。然后,为了使该机制能够运转,我们必须立即建立一个CommandBuilder对象。这次幕后的工作会变得更加复杂,但是分析它究竟是怎么工作的
是一件有趣的事情。
ADO.NET通过检查在DataSet对象中的特定表(在该例子中是ProductTable)的所有记录来决定对数据库中的哪条记录进行更新。当最初用数据库中的记录
填充DataSet对象时,ADO.NET会为每个字段保存两个副本:初始值和当前值。如果需要的话,可以通过在表达式中指定需要的值来分别访问这两个值,
代码如下所示:
 row.Item("UnitPrice",DataRowVersion.Original);
row.Item("UnitPrice",DataRowVersion.Current);
另外,每行都有一个名称为RowState的属性,该属性用于表示该行的当前状态。它可以是在System.Data.DataRowState命名空间中定义的5个枚举中的任一个
Added该行已经被添加到表中
Deleted该行已经从表中删除了。需要注意的是,这里删除是指该行仅仅被打上了删除标记,它还没有被实际删除。它就允许ADO.NET稍后再删除数据库中的
这些相应的行。
Detached该行不在表中。这种情况通常是,当创建了一个新的DataRow对象,但是还没有将它添加到表的Rows集合中或者是已经使用Romove()方法从表中删除了
该行。
Modififed该行已经被修改了。例如,您已经为该行中的一个字段指定了新的值。
Unchanged该行至今尚未变化。
当通过更改字段的值(就像我们在前面的例子中修改的UnitPrice字段)来修改记录时,ADO.net会将行的当前值更改为重新指定的值,并将 RowState属性值改为
Modified。然后,当调用数据适配器的 Update()方法时,ADO.NET会使用当前值来更新数据库中的相应字段。
   如果需要的话,可以调用DataRow对象的AcceptChanges(0方法,来将所有字段的初始值改为当前值
(该方法还会将RowState属性的值设置为Unchanged)。或者也可以调用DataTable的AcceptChange(0方法将变化更新到表中的所有行中。
  与AcceptChanges()方法作用相反的是RejectChanges()方法,它也同样对行和表这两个对象提供支持,作用是弃您已经对DataSet对象做出的全部修改。调用行对象
的RejectChanges()方法会将该行的RowState属性改回为Unchanged.如果调用表对象的RejectChanges()方法,会失去对所有行的改变,并且会将所有行的RowState属性
重新设置为Unchanged.调用表对象的RejectChanges()方法还会删除所有已经添加到表中的行。
  使用CommandBuilder
在前一章克们反复强调过要想运转数据适配器的Update() 方法,创建CommandBuilder对象是必不可少的。这里再次使用时,复杂程度的增加使得它对进一步探讨主题
是有帮助的。SqlCommandBuilder cb=new SqlCommandBuilder(adapter);
上述代码的结果是,SqlCommmandBuilder的构造函数会使用SQL UPDATE语句(基于适配器的SELECT命令)来创建一个SqlCommand对象,然后将该Command对象指定给数据适
配器的UpdateCommand属性。CommandBuilder类同样可以构建INSERT和DELETE命令,会在您创建实例时自动完成)
3.手工创建Update命令
  虽然 CommandBuider对象的确能够完成Update命令的创建工作,并且能够为您省去许多编码工作,但是它们也有它们的缺点。至少它们存在着如下限制:
*它们只能用于更单个数据库表中的记录。如果您有一个需要从两个或两个以上的表中提取记录的查询,CommandBuilder将不能为您构建Update 命令。
*指定给DataAdapter对象的SelectCommand属性的SQL语句必须要返回一个包含惟一识别返回记录的值的列。
*如果SelectCommand属性变化了,必须调用适配器对象的RefreshSchema()方法来更新元数据,它用于生成Insert、Update 和Delete命令。
如果您的情况符合上述限制中的任何一条,CommandBuilder对象都不会为您工作,您就必须手工来构建Update命令
SqlCommand cmd=nw SqlCommand("UPDATE Products SET UnitPrice=@Price WHERE ProductID=@ProductID",objConnection)
SqlParameter param=cmd.Parameters.Add("@ProductID",SqlDbType.Int);
param.SourceColumn="ProductID";
param.SourceVersion=DataRowVersion.Original;
param=cmd.Parameters.Add("@Price",SqlDbType.Money);
param.SourceColumn="UnitPrice";
param.SourceVersion=DataRowVersion.Current;
adapter.UpdateCommand=cmd;
在上述代码中,我们创建一个Command对象并把它的构造函数传给完成更新操作的SQLUPDATE语句(或存储过程)。因为此时我们不知道要对哪条记录进行更新,以及更新后的
价格是多少,我们指定的ID和产品的价格稍后会由确定参数的语句来提供。
 SQL语句必须包含您想要更新的表的每一列。如果您改变了一些字段的值,但是不包括UPDATE语句中有关列,不需要做任何改变来发现它们到数据库的踟径。
  接着我们为命令增加两个参数。第一个参数是 @ProductID,它是整数类型的--我们指定它应当被绑定到表中的ProductI列,并且通过参数SourceVersion属性将参数绑定
到数据库中该字段的初始值来确保我们正在处理的是正确的行。第二个参数是@Price,它绑定到UnitPrice列,所包含的是数值的当前值。
  完成了Command对象的定义以后,我们将其指定给 DataAdapter对象的UpdateCommand属性。当我们调用数据适配器的Update() 方法时,该命令会执行更新数据库中正确的产品
记录。

一个例子:

web.config中<configuration>
<appSettings>
<add key="strConnection" value="server=station-05\ggll;database=Northwind;uid=sa;pwd=sa" />
</appSettings>

cs中

using System.Data.SqlClient;
using System.Configuration;

namespace sp
{
/// <summary>
/// ChangePrice_Datagrid 的摘要说明。
/// </summary>
public class ChangePrice_Datagrid : System.Web.UI.Page
{
protected System.Web.UI.WebControls.DataGrid dgProducts;
private String strConnection=ConfigurationSettings.AppSettings["strConnection"];
private String strSQLSelect="SELECT ProductID,ProductName,UnitPrice FROM Products WHERE CategoryID=1";
private String ProductTableName="ProductTable";
private SqlConnection objConnection;

private void Page_Load(object sender, System.EventArgs e)
{

if(!IsPostBack)
LoadGrid();
// 在此处放置用户代码以初始化页面
}
private void LoadGrid()
{
Connect();
SqlDataAdapter adapter=new SqlDataAdapter(strSQLSelect,objConnection);
DataSet ds=new DataSet();
adapter.Fill(ds,ProductTableName);
Disconnect();
dgProducts.DataSource=ds.Tables[ProductTableName];
dgProducts.DataBind();



}
private void Connect()
{
objConnection=new SqlConnection(strConnection);
if(objConnection.State==ConnectionState.Closed)
objConnection.Open();

}
private void Disconnect()
{
objConnection.Close();

}

#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.dgProducts.CancelCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.dgProducts_CancelCommand);
this.dgProducts.EditCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.dgProducts_EditCommand);
this.dgProducts.UpdateCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.dgProducts_UpdateCommand);
this.dgProducts.SelectedIndexChanged += new System.EventHandler(this.dgProducts_SelectedIndexChanged);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void dgProducts_SelectedIndexChanged(object sender, System.EventArgs e)
{

}

private void dgProducts_EditCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
dgProducts.EditItemIndex=e.Item.ItemIndex;
LoadGrid();

}

private void dgProducts_CancelCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
dgProducts.EditItemIndex=-1;
LoadGrid();
}

private void dgProducts_UpdateCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
int ProductID=Convert.ToInt32(e.Item.Cells[0].Text);
TextBox PriceTextBox=(TextBox)e.Item.Cells[2].Controls[0];
decimal Price=Convert.ToDecimal(PriceTextBox.Text);
dgProducts.EditItemIndex=-1;
UpdateProduct(ProductID,Price);

}
private void UpdateProduct(int ProductID,decimal Price)
{

Connect();
SqlDataAdapter adapter=new SqlDataAdapter(strSQLSelect,objConnection);
DataSet ds=new DataSet();
adapter.Fill(ds,ProductTableName);
Disconnect();
DataTable tbl=ds.Tables[ProductTableName];
tbl.PrimaryKey=new DataColumn[]{tbl.Columns["ProductID"]};
DataRow row=tbl.Rows.Find(ProductID);
row["UnitPrice"]=Price;
SqlCommandBuilder cb=new SqlCommandBuilder(adapter);
Connect();
adapter.Update(ds,ProductTableName);
Disconnect();

dgProducts.DataSource=ds.Tables[ProductTableName];
dgProducts.DataBind();


}
}
}

 

 

 

 

 

 

(一)

有没有人遇到这种情况,用 SqlDataAdapter.Update(ds)更新时出错?

answer:   一般是这样的,如果用设计器将SqlDataAdapter拖到页面中使用时,不会出现这种情况,因为

                  系统会自动生成SqlDataAdapter的属性命令,比如: .UpdateCommane insertCommand

                  selectCommand等。 但是有些程序员不喜欢用设计器,或者是有些地方没必要拖动

                  SqlDataAdapter这么个庞大物来实现,那么SqlDataAdapter就不会自动生成相关的查询或更新

                  语句了.   所以当执行到SqlDataAdapter.Update(ds)语句时,SqlDataAdapter桥接器不知道更

                 新哪个表.不报错了.

(二)

解决方法:

SqlCommandBuilder 实现批量更新

1.功能:

   可以实现你对DataSet在UI层做任意操作后,直接丢给这个方法,这个方法就可以自动把你的修改更 新到数 据库中,而没必要每次都更新到

   数据库

2.使用方法
public int UpdateByDataSet(DataSet ds,string strTblName,string strConnection)
{
   try
{
   SqlConnection conn = new SqlConnection(strConnection));
        
   SqlDataAdapter myAdapter = new SqlDataAdapter();
   SqlCommand myCommand = new SqlCommand("select * from "+strTblName),(SqlConnection)this.conn);   
   myAdapter.SelectCommand = myCommand;
   SqlCommandBuilder myCommandBuilder = new SqlCommandBuilder(myAdapter);    
   myAdapter.Update(ds,strTblName);

   return 0;
}
catch(BusinessException errBU)
{
   throw errBU;
}  
catch(Exception err)
{
   throw new BusinessException(err);
}
}

直接调用这个方法就可以啦,说明的一点是select * from "+strTblName是一定要的,
作用大家也应该想到了,主要是告诉 SqlDataAdapter更新哪个表


3.什么时候用?

    a. 有时候需要缓存的时候,比如说在一个商品选择界面,选择好商品,并且进行编辑/删除/更新后,

       最后一并交给数据库,而不是每一步操作都访问数据库,因为客户选择商品可能进行n次编辑/删除

       更新操作,如果每次都提交,不但容易引起数据库冲突,引发错误,而且当数据量很大时在用户执行

       效率上也变得有些慢

    b.有的界面是这样的有的界面是这样的,需求要求一定用缓存实现,确认之前的操作不提交到库,点击

      页面专门提交的按钮时才提交商品选择信息和商品的其它信息. 我经常遇到这样的情况

    c.有些情况下只往数据库里更新,不读取. 也就是说没有从数据库里读,SqlDataAdapter也就不知道是
    
      更新哪张表了,调用Update就很可能出错了。这样的情况下可以用SqlCommandBuilder 了.
        

4.
注意点:
1.只能更新一个表,不能更新两个或两个以上相关联的表
2.表中必须有主键
3.更新的表中字段不能有image类型的

5.优点:

    节省代码量,节省时间,这个方法可以代替所有的: 更新/删除/插入操作语句

6.缺点:
        访问两次数据库(select * TableName,就是这句,要确认是哪个表,除非是很大的数据量,
        一般是感觉不到的),效率有些慢