这个问题:
> **“不能通过已删除的行访问该行的信息”**
通常是由于你在 C# 中调用了 `DataTable.GetChanges()` 方法,并且在删除操作中使用了 `DataRow.Delete()` 方法,但你又试图从这些已经被标记为 `Deleted` 状态的行中读取数据(例如 `guid_id`),从而导致异常。
---
### ✅ 问题原因
当你调用:
```csharp
DataTable changes = tb1.GetChanges();
```
如果 `tb1` 中存在已被调用 `.Delete()` 的行,那么这些行的状态是 `DataRowState.Deleted`。
如果你尝试访问这些行的字段(如 `row["guid_id"]`),就会抛出:
> **"Cannot access a row that has been deleted."**
---
### ✅ 解决方案
你需要从**原始 DataTable (`tb1`)** 中获取 `guid_id`,而不是从 `changes` 中获取。
---
### ✅ 修改后的 C# 删除逻辑(修复错误)
```csharp
private void SaveData()
{
try
{
System.Collections.Hashtable Para = new System.Collections.Hashtable();
DataTable tb1 = dgvGrid1.GetCommitTable().Copy(); // 原始数据表
// 判断是否为删除操作
bool isDeleteOnly = false;
List<string> deletedGuids = new List<string>();
foreach (DataRow row in tb1.Rows)
{
if (row.RowState == DataRowState.Deleted)
{
// 注意:从原始行(RowState.Deleted 的行)中读取字段要用 Original 值
deletedGuids.Add(row["guid_id", DataRowVersion.Original].ToString());
}
}
if (deletedGuids.Count > 0)
{
isDeleteOnly = true;
Para.Add("@str_delete", string.Join(",", deletedGuids)); // 传入要删除的 guid_id 列表
}
else
{
DataTable changes = tb1.GetChanges();
if (changes != null && changes.Rows.Count > 0)
{
Para.Add("@user_name1", PettyCashBasic.Classes.PettyCashLogin.UserName);
Para.Add("@tb1", PettyCashBasic.Classes.PettyCashConnection.GetTable101(changes));
}
else
{
MessageBox.Show("没有需要保存的变更。");
return;
}
}
if (PettyCashBasic.Classes.PettyCashConnection.DataUpdate("p_user_mapping_sav", Para))
{
tb1.AcceptChanges();
GetGridData();
if (isDeleteOnly)
MessageBox.Show("删除完成。");
else
MessageBox.Show("保存结束。");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
```
---
### ✅ 修改后的 SQL 存储过程(支持批量删除 guid_id)
```sql
USE [pettycash]
GO
ALTER PROCEDURE [dbo].[p_user_mapping_sav]
(
@user_name1 nvarchar(50) = '',
@tb1 diytbl readonly,
@str_delete nvarchar(max) = '' -- 逗号分隔的 guid_id 列表
)
AS
BEGIN
SET NOCOUNT ON;
-- 创建临时表#TT,用于插入/更新数据
SELECT
ISNULL(f0, '') guid_id,
ISNULL(f1, '') soft_name,
ISNULL(f2, '') host_user,
ISNULL(f3, '') card_name,
ISNULL(f4, '') user_name,
ISNULL(f5, '') version_id,
CASE
WHEN ISNULL(f6, '') = '' THEN CONVERT(nvarchar(50), GETDATE(), 112)
ELSE ISNULL(f6, '')
END AS begin_date,
CASE
WHEN ISNULL(f7, '') = '' THEN '20991231'
ELSE ISNULL(f7, '')
END AS end_date,
ISNULL(f8, '') rem_01,
ISNULL(f9, '') rem_02,
ISNULL(f10, '') rem_03,
GETDATE() AS ins_time,
@user_name1 AS ins_user,
GETDATE() AS upd_time,
@user_name1 AS upd_user
INTO #TT
FROM @tb1;
-- 删除逻辑:如果 @str_delete 不为空,则删除对应 guid_id 的记录
IF ISNULL(@str_delete, '') <> ''
BEGIN
-- 使用 STRING_SPLIT 解析逗号分隔的 guid_id 列表
DELETE u
FROM user_mapping u
INNER JOIN STRING_SPLIT(@str_delete, ',') s ON u.guid_id = s.value
END
ELSE
BEGIN
-- 插入新纪录
INSERT INTO user_mapping (
soft_name, host_user, card_name, user_name, version_id,
begin_date, end_date, rem_01, rem_02, rem_03,
ins_time, ins_user, upd_time, upd_user
)
SELECT
T.soft_name, T.host_user, T.card_name, T.user_name, T.version_id,
T.begin_date, T.end_date, T.rem_01, T.rem_02, T.rem_03,
T.ins_time, T.ins_user, T.upd_time, T.upd_user
FROM #TT T
LEFT JOIN user_mapping u ON u.guid_id = T.guid_id
WHERE u.guid_id IS NULL OR T.guid_id = ''
-- 更新已有记录
UPDATE u
SET
soft_name = T.soft_name,
host_user = T.host_user,
card_name = T.card_name,
user_name = T.user_name,
version_id = T.version_id,
begin_date = T.begin_date,
end_date = T.end_date,
rem_01 = T.rem_01,
rem_02 = T.rem_02,
rem_03 = T.rem_03,
upd_time = T.upd_time,
upd_user = T.upd_user
FROM #TT T
INNER JOIN user_mapping u ON u.guid_id = T.guid_id
WHERE T.guid_id <> ''
END
DROP TABLE #TT;
END
```
---
### ✅ 关键点总结
| 问题 | 解决方法 |
|------|----------|
| 访问 Deleted 行报错 | 使用 `row["guid_id", DataRowVersion.Original]` |
| 删除时传入 guid_id 列表 | 使用逗号分隔字符串 `@str_delete` |
| SQL 删除 | 使用 `STRING_SPLIT` 解析并删除 |
| 避免将删除行传入 `#TT` | 删除操作单独处理,不走 `@tb1` 插入更新流程 |
---
###