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

Posted

技术标签:

【中文标题】DataTable update() 插入重复的新行而不检查它是不是存在【英文标题】:DataTable update() inserts duplicate new rows without checking if it existsDataTable update() 插入重复的新行而不检查它是否存在 【发布时间】:2021-03-03 22:25:41 【问题描述】:

我正在尝试使用 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);

【问题讨论】:

填充数据表的代码在哪里? 如果您已经从数据库中加载了行,则更新只进行更新而不是插入。否则它不知道它的存在。 @DaleK 从数据库中加载行是什么意思,您能详细说明一下吗? 正是如此,您使用Fill() 方法和select 语句从数据库加载数据。您修改该数据,包括添加新行并将其更新回来。如果您在前端创建所有数据,这可能会重复现有数据,那么您的适配器不知道这一点,因为您尚未加载任何数据,因此它假定它是全新的。 @DaleK 从技术上讲这并不完全正确 - 如果数据行的状态为已修改,Update() 将执行更新,这与它的出处无关 【参考方案1】:

让我们看看发布的代码中发生了什么。 首先这一行:

 this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;

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

现在看这一行:

ServerDataTable.Rows.Clear();

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

DataRow toInsert = ServerDataTable.NewRow();

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

ServerDataTable.Rows.InsertAt(toInsert, 0);

然后DataRow.RowState 属性变为DataRowState.Added

此时缺少的信息是当您调用 Update 时 TableAdapter 的行为方式。适配器需要构建适当的 INSERT/UPDATE/DELETE sql 命令来更新数据库。用于选择正确 sql 命令的信息是什么?实际上,它查看了RowState 属性,发现所有行都处于已添加状态。因此,它为您的表选择 INSERT 命令,并禁止任何重复的键违规,您将在表中以重复记录结束。

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

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

如果您获得的行数大于零,那么您可以使用返回的第一行并使用您的更改更新现有值,如果没有符合条件的行,那么您可以像现在一样使用 InsertAt。

【讨论】:

请不要称它们为DataTableAdapters。有DataTables、DataAdapters 和TableAdapters。在那里扔另一个名字(不是东西的东西)真的会引起混乱! ? @Steve 感谢您的指点。我尝试使用 clear 的原因是因为我试图删除数据表中与我的插入不匹配的任何旧行,即只有我试图插入的内容应该在数据表中。有没有删除旧行的好方法? 那么您需要在执行更新之前对表执行直接 SQL 命令。有一个 TRUNCATE TABLE 命令。 我不同意史蒂夫那里的“需要”。没有“需要”发出原始截断。如果您想从已下载到 dt 的 db 中删除行,您只需 foreach(var r in dt)r.Delete(); 将他的行标记为已删除,然后 tableadapter Update 提交删除.. 根本没有“需要”发出原始 sql您可能希望使用直接服务器交互来转储数千行而不是将它们下载到客户端.. 但即使那样您也可能会向 tableadapter 添加自定义命令(右键单击数据集,添加查询,删除行的查询, delete from table例如) 我想注意的其他事情,在您的 Select 中(我不同意,这对于一般选择通用 dt 上的行很有用),如果它是 PK 列,则使用专用的 Find(强类型表中的 FindByxxx)或者这些天您可能会使用 LINQ(尤其是在强类型中,因为不需要强制转换)【参考方案2】:

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

// 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 是强类型还是弱类型,但如果它是强类型(即它存在于您的数据集中,或者属于您的数据集的一部分),如果您这样做,您将为自己带来巨大的好处 访问它的 Rows 集合。访问强类型数据表的方式就像访问数组一样

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 ServerDataTable 那样,这只会让工作变得更加困难,就像object o = new Person() 所说的那样——现在你每次想要用它做几乎任何人特定的事情时都必须投 o 。您总是可以将每个程序中的所有变量声明为类型对象..但您没有..因此不要通过将强类型数据表放在 DataTable 类型变量中来做等价的事情,因为您只是隐藏了这些东西这使它们变得有用且易于使用


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

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

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

...在现有行旁边插入更多行到数据库中,然后向数据表添加更多行

数据表会跟踪您对其行所做的操作。如果您清除数据表,它不会将每一行都标记为已删除,它只会丢弃这些行。不会影响任何 db 侧行。如果您删除行,那么它们会获得已删除的行状态,并且在您调用 adapter.Update 时会触发删除查询

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

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

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

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

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

【讨论】:

以上是关于DataTable update() 插入重复的新行而不检查它是不是存在的主要内容,如果未能解决你的问题,请参考以下文章

批量插入数据, 将DataTable里的数据批量写入数据库的方法

使用带参数的 SqlDataAdapter.Update()

MongoDB---如何避免插入重复数据(pymongo)

mysql ON DUPLICATE KEY UPDATE重复插入时更新

mysql ON DUPLICATE KEY UPDATE重复插入时更新

MySql避免重复插入记录方法(ignore,Replace,ON DUPLICATE KEY UPDATE)