使用TableAdapter.Update同步DataTable与服务端表时出现重复插入、不删除冗余行的问题
异常根因说明
TableAdapter.Update() 本身不具备“把本地DataTable全量镜像同步到SQL Server表”的能力,它只会根据DataTable内部每一行的RowState属性,生成对应的数据库操作语句。你现在遇到的重复插入、不删旧行的问题,完全是行状态标记错误导致的:
- 你用
Rows.Clear()清空本地行时,行的RowState不会被标记为Deleted,TableAdapter根本感知不到删除需求,自然不会向服务端发DELETE语句 - 你循环插入的新行默认
RowState是Added,TableAdapter不会主动校验服务端是否存在同主键数据,直接下发INSERT语句,就会产生重复数据 - 你在更新前才临时给DataTable设置主键,主键匹配逻辑已经无法生效,没法自动识别重复的同主键行
修复实现步骤
1. 修正本地行状态标记逻辑
放弃“直接清空再加行”的写法,改成逐行比对、正确标记行状态的流程,确保TableAdapter能感知到所有需要的增删变更:
private void convertGUIToTableFormat() { // 把现有本地表的所有行标记为待删除,不要直接调用Rows.Clear() foreach (DataRow row in ServerDataTable.Rows) { row.Delete(); } // 遍历生成需要同步到服务端的目标行数据 for (int i = 0; i < GUIDataTable.Rows.Count; i++) { for (int d = 1; d < 8; d++) { if ((bool)GUIDataTable.Rows[i][d] == true) { // 用主键查找有没有匹配的待删除行 DataRow matchedRow = ServerDataTable.Rows.Find(new object[] { GUIDataTable.Rows[i][0], d.ToString(), GUIDataTable.Rows[i][8], GUIDataTable.Rows[i][9] }); if (matchedRow != null) { // 匹配到同主键的待删除行,恢复为未变更状态,就不会触发DELETE+INSERT的重复操作 matchedRow.RejectChanges(); } else { // 没有匹配到的行才新增,默认RowState为Added DataRow newRow = ServerDataTable.NewRow(); newRow[0] = GUIDataTable.Rows[i][0]; newRow[1] = d.ToString(); newRow[2] = GUIDataTable.Rows[i][8]; newRow[3] = GUIDataTable.Rows[i][9]; ServerDataTable.Rows.Add(newRow); } } } } }
处理后的行状态会完全匹配服务端同步需求:
- 不在当前同步范围内的旧行保持
Deleted状态,Update时自动触发DELETE语句清理服务端冗余数据- 两边都存在的同主键行保持
Unchanged状态,不触发多余的数据库操作- 新生成的同步行保持
Added状态,Update时自动触发INSERT语句
2. 提前配置DataTable主键
不要等执行Update前才设置主键,要在DataTable首次加载完成后第一时间配置主键,让行匹配逻辑可以正常生效:
private void Form1_Load(object sender, EventArgs e) { // 加载服务端初始数据 this.vendor_GUI_Test_DataTableAdapter.Fill(this.MyDatabaseDataSet11.Vendor_GUI_Test_Data); this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data; // 加载完成后立刻设置主键 ServerDataTable.PrimaryKey = new DataColumn[] { ServerDataTable.Columns["Vendor"], ServerDataTable.Columns["DOW"], ServerDataTable.Columns["LeadTime"], ServerDataTable.Columns["DemandPeriod"] }; }
3. 简单全量同步的兜底方案
如果你的表数据量不大,不需要精细的行级增量同步,可以用更直接的全量覆盖逻辑,彻底避开行状态匹配的问题:
// 全量同步实现 // 1. 先清空服务端目标表 vendor_GUI_Test_DataTableAdapter.Connection.Open(); var clearCmd = new SqlCommand("DELETE FROM Vendor_GUI_Test_Data", vendor_GUI_Test_DataTableAdapter.Connection); clearCmd.ExecuteNonQuery(); // 2. 把本地所有有效行标记为待新增 foreach (DataRow row in ServerDataTable.Rows) { if (row.RowState == DataRowState.Deleted) { row.RejectChanges(); } row.SetAdded(); } // 3. 批量执行插入 vendor_GUI_Test_DataTableAdapter.Update(ServerDataTable);
注意:如果表没有其他外键关联,可以把
DELETE换成TRUNCATE,执行速度会更快。
内容的提问来源于stack exchange,提问作者Lightsout
相关产品推荐
相关产品推荐

