DataTable update() 插入重复的新行而不检查它是否存在



>我正在尝试使用 update() 方法,但它将我的数据表数据插入我的数据库而不检查该行是否存在,因此它插入了重复的数据。 它也不会删除数据表中不存在的行。 如何解决这个问题? 我想将数据表与服务器表同步。

private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'MyDatabaseDataSet11.Vendor_GUI_Test_Data' table. You can move, or remove it, as needed.
this.vendor_GUI_Test_DataTableAdapter.Fill(this.MyDatabaseDataSet11.Vendor_GUI_Test_Data);
// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
}

插入

private void convertGUIToTableFormat()
{
ServerDataTable.Rows.Clear();
// loop through GUIDataTable rows
for (int i = 0; i < GUIDataTable.Rows.Count; i++)
{
String guiKEY = (String)GUIDataTable.Rows[i][0] + "," + (String)GUIDataTable.Rows[i][8] + "," + (String)GUIDataTable.Rows[i][9];
//Console.WriteLine("guiKey: " + guiKEY);
// loop through every DOW value, make a new row for every true
for(int d = 1; d < 8; d++)
{
if ((bool)GUIDataTable.Rows[i][d] == true)
{
DataRow toInsert = ServerDataTable.NewRow();
toInsert[0] = GUIDataTable.Rows[i][0];
toInsert[1] = d + "";
toInsert[2] = GUIDataTable.Rows[i][8];
toInsert[3] = GUIDataTable.Rows[i][9];
ServerDataTable.Rows.InsertAt(toInsert, 0);
//printDataRow(toInsert);
//Console.WriteLine("---------------");
}
}
}

正在尝试更新

// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);

让我们看看发布的代码中发生了什么.
第一行:

this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;

这不是副本,而只是两个变量之间的赋值。分配的(ServerDataTable)接收对存储来自数据库的数据的内存区域的"引用"。因此,这两个变量"指向"相同的内存区域。无论你对一个做什么都会影响另一个看到的东西。

现在看看这一行:

ServerDataTable.Rows.Clear();

呃!为什么?您正在清除从数据库加载的数据所在的内存区域。现在数据表是空的,那里没有记录(数据行).
让我们看看循环内部发生了什么

DataRow toInsert = ServerDataTable.NewRow();

已经创建了一个新的 DataRow,现在每个 DataRow 都有一个名为 RowState 的属性,当您创建新行时,此属性的默认值为DataRowState.Detached,但是当您在 DataRow 集合中添加该行时

ServerDataTable.Rows.InsertAt(toInsert, 0);

DataRow.RowState属性变为DataRowState.Added

此时缺少的信息是 TableAdapter 在调用Update时的行为方式。适配器需要构建相应的插入/更新/删除 sql 命令来更新数据库。用于选择正确的 sql 命令的信息是什么?实际上,它会查看RowState属性,并看到所有行都处于"已添加"状态。因此,它会为您的表选择 INSERT 命令,除非发生任何重复键冲突,否则您将以重复记录结束您的表。

您应该如何解决问题?好吧,第一件事是从加载的数据中删除清除内存的行,然后,而不是调用总是InsertAt您应该首先查看内存中是否已经有该行。您可以使用 DataTable.Select 方法执行此操作。此方法需要一个字符串,就像它是一个 WHERE 语句一样,你应该为表的主键使用一些值

var rows =  ServerDataTable.Select("PrimaryKeyFieldName = " + valueToSearchFor);

如果行数大于零,则可以使用返回的第一行并使用更改更新现有值,如果没有与条件匹配的行,则可以像现在一样使用 InsertAt。

我认为你太努力了,不幸的是,你几乎把所有事情都弄错

// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;

不,这行代码根本不会对数据库执行任何操作,它只是将现有数据表分配给名为 ServerDataTable 的属性。

for (int i = 0; i < GUIDataTable.Rows.Count; i++)

目前尚不清楚 GUIDataTable 是强类型还是弱类型,但如果它是强类型的(即它存在于您的数据集中,或者属于您的数据集的一部分),如果您根本不访问它的行集合,您将为自己带来巨大的帮助。访问强类型数据表的方式就像它是一个数组一样

myStronglyTypedTable[2] //yes, third row
myStronglyTypedTable.Rows[2] //no, do not do this- you end up with a base type DataRow that is massively harder to work with

然后我们有..

DataRow toInsert = ServerDataTable.NewRow();

同样,不要这样做......您正在使用强类型数据表。这让您的生活变得轻松:

var r = MyDatabaseDataSet11.Vendor_GUI_Test_Data.NewVendor_GUI_Test_DataRow();

因为现在你可以通过名称和类型来引用所有内容,而不是数字索引和对象:

r.Total = r.Quantity * r.Price; //yes
toInsert["Ttoal"] = (int)toInsert["Quantity"] * (double)toInsert["Price"]; //no. Messy, hard work, "stringly" typed, casting galore, no intellisense.. The typo was deliberate btw

您还可以轻松地将数据添加到类型化数据表中,例如:

MyPersonDatatable.AddPersonRow("John, "smith", 29, "New York");

接下来..

// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);

需要在这里理顺你脑海中的概念和术语......这不是适配器,它不是来自 DataGridView,网格视图从不提供适配器,你的 datatable 变量始终是它们的格式,如果你把它键入为DataTable ServerDataTable那么这只会让它更难使用,就像说object o = new Person()一样 - 现在你必须每次你想做几乎任何事情时都强制转换 o具体的人。你总是可以在每个程序中声明所有变量,作为类型对象。但你没有..因此,不要通过将强类型数据表放在 DataTable 类型变量中来执行等效操作,因为您只是隐藏了使它们有用且易于使用的东西


如果将行从数据库下载到数据表中,并且想要...

。从数据库中删除它们,然后在数据表中对它们调用 Delete

。在数据库中更新它们,然后在数据表中的现有行上设置新值

。在数据库中与现有行一起插入更多行,然后向数据表添加更多行

数据表跟踪您对其行执行的操作。如果清除数据表,它不会将每一行都标记为已删除,它只是丢弃这些行。数据库侧行不会受到影响。如果删除行,则它们将获得已删除的行状态,并且在调用适配器时将触发删除查询。更新

修改行以触发更新。添加新行以进行插入

正如史蒂夫所指出的,你抛弃了所有的行,添加了新行,添加了(可能无用的)一个主键(强类型表可能已经有了这个键),这并不意味着新行会自动关联到旧行/不会导致它们被更新,他插入了大量新行并将它们写入数据库。此过程永远不会更新或删除任何内容

这应该的工作方式是,你下载行,你在网格中看到它们,你添加一些,你改变一些,你删除一些,你点击保存按钮。在幕后,网格只是在数据表中戳了一些新行,将一些标记为已删除,更改了其他行。它没有达到您的代码所达到的巨大(不幸的是不正确)长度。如果您希望代码的行为相同,请遵循相同的想法:

var pta = new PersonTableAdapter();
var pdt = pta.GetData(); //query that returns all rows
pta.Fill(somedataset.Person); //or can do this
pdt = somedataset.Person;     //alias of Person table
var p = pdt.FindByPersonId(123); //PersonId is the primary key in the datatable
p.Delete(); //mark person 123 as deleted
p = pdt.First(r => r.Name = "Joe"); //LINQ just works on strongly typed datatables, out of the box, no messing
p.Name = "John"; //modify joes name to John
pdt.AddPersonRow("Jane", 22);
pta.Update(pdt); //saves changes(delete 123, rename joe, add Jane) to db

您需要了解的是,所有这些命令都只是查找或创建位于表中的数据行obj3cts..表跟踪您的操作,适配器使用适当的SQL将更改发送到数据库。 如果要将数据表中的所有行标记为已删除,可以访问它们中的每一行并在其上调用Delete(), 然后更新数据表以保存对数据库所做的更改

最新更新