今天在讲ATA的ADO.NET2.0里面的一个案例的时候,出现了一个问题。具体的说,就是在使用DataAdapter进行数据更新的时候,出现了一个“不返回任何键列信息的SelectCommand不支持UpdateCommand的动态SQL生成”错误。
当我们使用适配器从数据库中读到数据,然后填充到DataSet中以后,如何要更新数据,那么,需要先改变DataSet中数据的值(也就是内存中的“副本”的值),然后再更新数据库,或者说叫同步数据库。一般我们都是使用SqlCommandBuilder来完成的(当然,如果出于性能考虑,还可以直接指定命令而不使用CommandBuilder,另外,也不宜使用 CommandBuilder 来更新参与外键约束的列)。
出现这个错误的主要原因是:
1、数据库中的表没有指定主键!因为我们更新数据,是先更新DataSet中的数据,然后同步数据库里面相应的表,这就需要主键来查找相关的记录,从而实现更新(所以,大家会发现,updateCommand和DeleteCommand就会出错,但是InsertCommand却不会出错)。
2、需要在DataSet中指定表的主键,或者让它自动带上相关的表架构,也就是说select语句中必须有select 主键字段 from ……。
这里直接将演示代码粘贴出来得了:
表的结构如图所示:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace ConsoleApplication1
{
class Program
{
SqlConnection scon;
string strcon;
SqlDataAdapter sda;
SqlCommand scmd;
DataTable employeeDataTable;
DataSet ds;
SqlCommandBuilder scb;
public Program()
{
strcon = "Data Source =localhost; Initial Catalog=adventureWorks; Integrated Security=yes";
scon = new SqlConnection(strcon);
scmd = scon.CreateCommand();
scmd.CommandText = "select EmployeeID,Title from dbo.Employees";
sda = new SqlDataAdapter(scmd);
ds=new DataSet();
employeeDataTable = new DataTable("Employees");
ds.Tables.Add(employeeDataTable);
sda.Fill(ds,"Employees");
employeeDataTable.PrimaryKey = new DataColumn[] { employeeDataTable.Columns["EmployeeID"]};
//出错就是因为少了上面这一句。这条语句指定了DataTable的主键。或者用下一条语句也可以,下一条语句是让适配器自动加上表的架构(Key约束)
// sda.MissingSchemaAction = MissingSchemaAction.AddWithKey;
}
public void actionOne()
{
DataRow dr = employeeDataTable.NewRow();
dr = employeeDataTable.Rows[0];
dr["EmployeeID"] = 11; //要在DataTable中修改一行数据
dr = employeeDataTable.NewRow();
dr["EmployeeID"] = 99;
dr["Title"] = "vice president";
employeeDataTable.Rows.Add(dr); //要往DataTable中插入一行数据
dr = employeeDataTable.Rows[1];
dr.Delete(); //要删掉DataTable中的一行数据
scb = new SqlCommandBuilder(sda); //使用SqlCommandBuilder
sda.UpdateCommand = scb.GetUpdateCommand();
Console.WriteLine("update command : " + sda.UpdateCommand.CommandText+"\n");
//通过CommandBuilder来获得命令,并打印在控制台;后面是不同的命令类型
sda.InsertCommand = scb.GetInsertCommand();
Console.WriteLine("insert command: " + sda.InsertCommand.CommandText+"\n");
sda.DeleteCommand = scb.GetDeleteCommand();
Console.WriteLine("delete command : " + sda.DeleteCommand.CommandText + "\n");
sda.Update(ds,"Employees"); //更新Employees表
ds.Acceptchanges(); //更新完数据源以后,在dataset中接受变化,更新数据集
}
static void Main(string[] args)
{
Program p=new Program();
p.actionOne();
}
}
}
代码可以直接ctrl+c 拷贝
关于如何显式指定命令来更新数据源,我直接拷贝了一段msdn上的代码,大家可以参考以下:
private static void AdapterUpdate(string connectionString)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlDataAdapter dataAdpater = new SqlDataAdapter(
"SELECT CategoryID, CategoryName FROM Categories",
connection);
dataAdpater.UpdateCommand = new SqlCommand(
"UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID", connection);
dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter parameter = dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryID", SqlDbType.Int);
parameter.SourceColumn = "CategoryID";
parameter.SourceVersion = DataRowVersion.Original;
DataTable categoryTable = new DataTable();
dataAdpater.Fill(categoryTable);
DataRow categoryRow = categoryTable.Rows[0];
categoryRow["CategoryName"] = "New Beverages";
dataAdpater.Update(categoryTable);
Console.WriteLine("Rows after update.");
foreach (DataRow row in categoryTable.Rows)
{
{
Console.WriteLine("{0}: {1}", row[0], row[1]);
}
}
}
}