如何使用c#.net中 Dataset.update()
这是今天写程序遇到最大的麻烦,而且关于这个问题看了很多网上的资料,困惑很久,真不知道微软把它拿来干什么的。今天终于通过不断的尝试,获得了成功,特在此篇博客分享一下吧。
『前言』:
最近在编写一个关于股票交易的模拟数据交易平台,所以要常常和数据库打交道。而微软一直把dataset奉为至宝,所以我也采用了这个工具作为我内存数据和数据库信息的交换通道。
这是我的一个程序片段
private void market_buy()
{
int left = volume;
SqlDataAdapter da = new SqlDataAdapter("select price,volume
from Table_order_sell where stock_code="+stock_code+"order by price
DESC", cn);
cn.Open();
//da.DeleteCommand
DataSet ds = new DataSet();
da.Fill(ds);
int rows=ds.Tables[0].Rows.Count;
for (int i = 0; i<rows;i++)
{
int temp = Convert.ToInt32(ds.Tables[0].Rows[i]["volume"].ToString());
if (left > temp)
{
left -= temp;
temp = 0;//删除当前行
ds.Tables[0].Rows[i].Delete();
}
else if (left < temp)
{
temp -= left;
ds.Tables[0].Rows[i]["volume"] = temp;
left = 0;
break;
}
else
{
ds.Tables[0].Rows[i].Delete();
left = 0;
break;
}
}
da.Update (ds.Tables [0]);
cn.Close();
return;
}
出现的一个错误为:当传递具有已删除行的 DataRow 集合时,更新要求有效的 DeleteCommand
仔细参考部分资料:
1.网上有很多馊主意,例如ds.Tables[0].AcceptChanges();然后再update;结果是错误没有报告,但是数据库并没有改变。还有人自作聪明的提议自己写DeleteCommand,但是自己又不会实现,也查不到这方面的资料,真是晕倒。最后终于有一位大虾,给了一个使用 SqlCommandBuilder 的建议,并且写了一个不错的demo,最后将原程序片段改为
private void market_buy()//市价买交易
{
int left = volume;
SqlDataAdapter da =
new SqlDataAdapter("select price,volume from Table_order_sell where
stock_code="+stock_code+"order by price DESC", cn);
cn.Open();
DataSet ds = new DataSet();
da.Fill(ds);
SqlCommandBuilder objcmdBuilder = new SqlCommandBuilder(da);
da.UpdateCommand = objcmdBuilder.GetUpdateCommand();
da.InsertCommand = objcmdBuilder.GetInsertCommand();
da.DeleteCommand = objcmdBuilder.GetDeleteCommand();
int rows=ds.Tables[0].Rows.Count;
for (int i = 0; i<rows;i++)
{
int temp = Convert.ToInt32(ds.Tables[0].Rows[i]["volume"].ToString());
if (left > temp)
{
left -= temp;
temp = 0;//删除当前行
ds.Tables[0].Rows[i].Delete();
}
else if (left < temp)
{
temp -= left;
ds.Tables[0].Rows[i]["volume"] = temp;
left = 0;
break;
}
else
{
ds.Tables[0].Rows[i].Delete();
left = 0;
break;
}
}
da.Update (ds.Tables [0]);
cn.Close();
return;
}
但问题没有想象中的简单,编译器依然报错:不返回任何键列信息的 SelectCommand,不支持 UpdateCommand、DeleteCommand 的动态 SQL 生成
意思是说:DataAdapter 填充的DataTable没有主键或者是数据库中对应的这个表没有主键。
于是,我又乖乖的在在对应的表中加入了ID自增字段,在sqlserver中需要用sql脚本来编写.
create table t
(
ID int indentity(1,1) not null primary key
)
至此该问题得到解决!~0~
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
namespace winApplication
{
public class sqlAccess
{
//与SQL Server的连接字符串设置
private string _connString;
private string _strSql;
private SqlCommandBuilder sqlCmdBuilder;
private DataSet ds = new DataSet();
private SqlDataAdapter da;
public sqlAccess(string connString,string strSql)
{
this._connString=connString;
}
private SqlConnection GetConn()
{
try
{
SqlConnection Connection = new SqlConnection(this._connString);
Connection.Open();
return Connection;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"数据库连接失败");
throw;
}
}
//根据输入的SQL语句检索数据库数据
public DataSet SelectDb(string strSql,string strTableName)
{
try
{
this._strSql = strSql;
this.da = new SqlDataAdapter(this._strSql,this.GetConn());
this.ds.Clear();
this.da.Fill(ds,strTableName);
return ds;//返回填充了数据的DataSet,其中数据表以strTableName给出的字符串命名
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"数据库操作失败");
throw;
}
}
//数据库数据更新(传DataSet和DataTable的对象)
public DataSet UpdateDs(DataSet changedDs,string tableName)
{
try
{
this.da = new SqlDataAdapter(this._strSql,this.GetConn());
this.sqlCmdBuilder = new SqlCommandBuilder(da);
this.da.Update(changedDs,tableName);
changedDs.AcceptChanges();
return changedDs;//返回更新了的数据库表
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,"数据库更新失败");
throw;
}
}
使用说明总结:
1. GetConn方法创建一个数据库连接,返回SqlConnection。
2.使用的select命令中必须包含主键,这点大家都知道的!
3. this.da.Fill(ds,strTableName) 填充数据集
4.构造CommandBuilder对象时,将DataAdapter对象作为构造函数参数传入:
this.sqlCmdBuilder = new SqlCommandBuilder(da);
5. 在调用UpdateDs()更新数据库前,请检查changedDs是否已经被更新过,用changedDs.[tableName] GetChanges() != null;
6.用this.da.Update(changedDs,tableName)方法更新数据,然后调用changedDs.AcceptChanges()才能真正的更新数据库,调用 changedDs.RejectChanges() 取消更新。
DataAdapter 的 Update 方法可调用来将 DataSet 中的更改解析回数据源。与 Fill 方法类似,Update 方法将 DataSet 的实例和可选的 DataTable 对象或 DataTable 名称用作参数。DataSet 实例是包含已作出的更改的 DataSet,而 DataTable 标识从其中检索更改的表。
当调用 Update 方法时,DataAdapter 将分析已作出的更改并执行相应的命令(INSERT、UPDATE 或 DELETE)。当
DataAdapter 遇到对 DataRow 的更改时,它将使用 InsertCommand、UpdateCommand 或
DeleteCommand 来处理该更改。这样,您就可以通过在设计时指定命令语法并在可能时通过使用存储过程来尽量提高 ADO.NET
应用程序的性能。在调用 Update 之前,必须显式设置这些命令。如果调用了 Update
但不存在用于特定更新的相应命令(例如,不存在用于已删除行的 DeleteCommand),则将引发异常。
Command 参数可用于为 DataSet 中每个已修改行的 SQL 语句或存储过程指定输入和输出值。有关更多信息,请参阅将参数用于 DataAdapter。
如果 DataTable 映射到单个数据库表或从单个数据库表生成,则可以利用 CommandBuilder 对象自动生成 DataAdapter
的 DeleteCommand、InsertCommand 和 UpdateCommand。有关更多信息,请参阅自动生成的命令。
Update 方法会将更改解析回数据源,但是自上次填充 DataSet 以来,其他客户端可能已修改了数据源中的数据。若要使用当前数据刷新
DataSet,请再次使用 DataAdapter 填充 (Fill) DataSet。新行将添加到该表中,更新的信息将并入现有行。
若要处理可能在 Update *作过程中发生的异常,可以使用 RowUpdated 事件在这些异常发生时响应行更新错误(请参阅使用
DataAdapter 事件),或者可以在调用 Update 之前将 DataAdapter.ContinueUpdateonError 设置为
true,然后在 Update 完成时响应存储在特定行的 RowError 属性中的错误信息(请参阅添加和读取行错误信息)。
注意 如果对 DataSet、DataTable 或 DataRow 调用 AcceptChanges,则将使某 DataRow 的所有
Original 值被该 DataRow 的 Current 值改写。如果已修改将该行标识为唯一行的字段值,那么当调用
AcceptChanges 后,Original 值将不再匹配数据源中的值。
以下示例演示如何通过显式设置 DataAdapter 的 UpdateCommand 来执行对已修改行的更新。请注意,在 UPDATE 语句的
WHERE 子句中指定的参数设置为使用 SourceColumn 的 Original 值。这一点很重要,因为 Current
值可能已被修改,并且可能不匹配数据源中的值。Original 值是曾用来从数据源填充 DataTable 的值。
SqlClient
[Visual Basic]
Dim catDA As SqlDataAdapter = New SqlDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn)
catDA.UpdateCommand = New SqlCommand("UPDATE Categories SET CategoryName = @CategoryName " & _
"WHERE CategoryID = @CategoryID", nwindConn)
catDA.UpdateCommand.Parameters.Add("@CategoryName", SqlDbType.NVarChar, 15, "CategoryName")
Dim workParm As SqlParameter = catDA.UpdateCommand.Parameters.Add("@CategoryID", SqlDbType.Int)
workParm.SourceColumn = "CategoryID"
workParm.SourceVersion = DataRowVersion.Original
Dim catDS As DataSet = New DataSet
catDA.Fill(catDS, "Categories")
Dim cRow As DataRow = catDS.Tables("Categories").Rows(0)
cRow("CategoryName") = "New Category"
catDA.Update(catDS)
[C#]
SqlDataAdapter catDA = new SqlDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn);
catDA.UpdateCommand = new SqlCommand("UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID" , nwindConn);
catDA.UpdateCommand.Parameters.Add("@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter workParm = catDA.UpdateCommand.Parameters.Add("@CategoryID", SqlDbType.Int);
workParm.SourceColumn = "CategoryID";
workParm.SourceVersion = DataRowVersion.Original;
DataSet catDS = new DataSet();
catDA.Fill(catDS, "Categories");
DataRow cRow = catDS.Tables["Categories"].Rows[0];
cRow["CategoryName"] = "New Category";
catDA.Update(catDS);
OleDb
[Visual Basic]
Dim catDA As OleDbDataAdapter = New OleDbDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn)
catDA.UpdateCommand = New OleDbCommand("UPDATE Categories SET CategoryName = ? " & _
"WHERE CategoryID = ?" , nwindConn)
catDA.UpdateCommand.Parameters.Add("@CategoryName", OleDbType.VarChar, 15, "CategoryName")
Dim workParm As OleDbParameter = catDA.UpdateCommand.Parameters.Add("@CategoryID", OleDbType.Integer)
workParm.SourceColumn = "CategoryID"
workParm.SourceVersion = DataRowVersion.Original
Dim catDS As DataSet = New DataSet
catDA.Fill(catDS, "Categories")
Dim cRow As DataRow = catDS.Tables("Categories").Rows(0)
cRow("CategoryName") = "New Category"
catDA.Update(catDS)
[C#]
OleDbDataAdapter catDA = new OleDbDataAdapter("SELECT CategoryID, CategoryName FROM Categories", nwindConn);
catDA.UpdateCommand = new OleDbCommand("UPDATE Categories SET CategoryName = ? " +
"WHERE CategoryID = ?" , nwindConn);
catDA.UpdateCommand.Parameters.Add("@CategoryName", OleDbType.VarChar, 15, "CategoryName");
OleDbParameter workParm = catDA.UpdateCommand.Parameters.Add("@CategoryID", OleDbType.Integer);
workParm.SourceColumn = "CategoryID";
workParm.SourceVersion = DataRowVersion.Original;
DataSet catDS = new DataSet();
catDA.Fill(catDS, "Categories");
DataRow cRow = catDS.Tables["Categories"].Rows[0];
cRow["CategoryName"] = "New Category";
catDA.Update(catDS);
自动递增列
如果来自数据源的表包含自动递增列,则可以使用由数据源生成的值填充 DataSet
中的列,方法是通过以存储过程输出参数的形式返回自动递增值并将其映射到表中的一列,或者使用 DataAdapter 的 RowUpdated
事件。有关示例,请参阅检索“标识”或“自动编号”值。
但是,DataSet 中的值可能会与数据源中的值不同步并导致意外的行为。例如,请考虑一个包含自动递增主键列 CustomerID 的表。如果在该
DataSet 中添加两个新客户,它们将收到自动递增的 CustomerId 值 1 和 2。在向 DataAdapter 的 Update
方法传递第二个客户行时,新添加的行会收到数据源中的自动递增 CustomerID 值 1,该值与 DataSet 中的值 2 不匹配。当
DataAdapter 使用返回值填充 DataSet 中的行时,由于第一个客户行的 CustomerID 已经是 1,因此将发生约束冲突。
为了避免这种行为,建议在使用数据源中的自动递增列和 DataSet 中的自动递增列时,在 DataSet 中创建
AutoIncrementStep 为 -1 且 AutoIncrementSeed 为 0 的列,并确保数据源生成从 1
开始并以正步长值递增的自动递增标识值。这样,DataSet
将为自动递增值生成负数,这些负数不会与数据源所生成的正自动递增值发生冲突。另一种方法是使用 Guid 类型的列而不是自动递增列。生成 Guid
值的算法在 DataSet 中生成的 Guid 从不会与数据源生成的 Guid 相同。有关定义 DataTable
中的列的更多信息,请参阅定义数据表的架构。
插入、更新和删除的排序
在许多情况下,以何种顺序向数据源发送通过 DataSet 作出的更改是相当重要的。例如,如果已更新现有行的主键值并且添加了具有新主键值的新行,则务必要在处理插入之前处理更新。
可以使用 DataTable 的 Select 方法来返回仅引用具有特定 RowState 的 DataRow 数组。然后可以将返回的
DataRow 数组传递到 DataAdapter 的 Update
方法来处理已修改的行。通过指定要更新的行的子集,可以控制处理插入、更新和删除的顺序。
例如,以下代码确保首先处理表中已删除的行,然后处理已更新的行,然后处理已插入的行。
[Visual Basic]
Dim updTable As DataTable = custDS.Tables("Customers")
'' First process deletes.
custDA.Update(updTable.Select(Nothing, Nothing, DataViewRowState.Deleted))
'' Next process updates.
custDA.Update(updTable.Select(Nothing, Nothing, DataViewRowState.ModifiedCurrent))
'' Finally, process inserts.
custDA.Update(updTable.Select(Nothing, Nothing, DataViewRowState.Added))
[C#]
DataTable updTable = custDS.Tables["Customers"];
// First process deletes.
custDA.Update(updTable.Select(null , null , DataViewRowState.Deleted));
// Next process updates.
custDA.Update(updTable.Select(null , null , DataViewRowState.ModifiedCurrent));
// Finally, process inserts.
custDA.Update(updTable.Select(null , null , DataViewRowState.Added));
using System;
using System.Data;
using System.Data.SqlClient;
namespace MyDataset1
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
SqlDataAdapter adapter=new SqlDataAdapter("select * from titles",
"server=localhost;database=pubs;uid=sa;pwd=");
SqlCommandBuilder builder=new SqlCommandBuilder(adapter);
DataSet ds=new DataSet();
adapter.Fill(ds,"Titles");
DataTable table=ds.Tables["Titles"];
DataRow[] rows=table.Select("price=59.99");//选取
foreach(DataRow row1 in rows)
row1.Delete();
DataRow row=table.NewRow();
row["title_id"]="JP1004";
row["title"]="Programming Microsoft .NET";
row["price"]="59.99";
row["ytd_sales"]="1000000";
row["type"]="business";
row["pubdate"]="2004-2-10";
table.Rows.Add(row);
//减少数据量
//DataTable delta=table.GetChanges();
//adapter.Update(delta);
//增加时
//DataTable inserts=table.GetChanges(DataRowState.Added);
//adapter.Update(inserts);
//控制更新的顺序
DataTable deletes=table.GetChanges(DataRowState.Deleted);
adapter.Update(deletes);
DataTable inserts=table.GetChanges(DataRowState.Added);
adapter.Update(inserts);
Console.WriteLine("OK");
}
}
}