AddWithKey 相关技术

本文介绍如何使用DataAdapter的Fill和FillSchema方法正确填充DataSet,确保主键约束的同步,并处理多个结果集的情况。

 

DataAdapter 的 Fill 方法仅使用数据源中的表列和表行来填充 DataSet;虽然约束通常由数据源来设置,但在默认情况下,Fill 方法不会将此架构信息添加到 DataSet 中。若要使用数据源中的现有主键约束信息填充 DataSet,则可以调用 DataAdapter 的 FillSchema 方法,或者在调用 Fill 之前将 DataAdapter 的 MissingSchemaAction 属性设置为 AddWithKey。这将确保 DataSet 中的主键约束反映数据源中的主键约束。外键约束信息不包含在内,将需要显式创建,如将约束添加到表所示。

如果在使用数据填充 DataSet 之前向其中添加架构信息,将确保将主键约束与 DataSet 的 DataTable 对象包含在一起。这样,当再次调用 Fill 来填充 DataSet 时,将使用主键列信息来匹配数据源中的新行和每个 DataTable 中的当前行,并且使用数据源中的数据改写表中的当前数据。如果没有架构信息,来自数据源的新行将追加到 DataSet,从而导致重复的行。

注意 如果数据源中的某列被标识为自动递增列,则 MissingSchemaAction 为 AddWithKey 的 FillSchema 方法或 Fill 方法将创建一个 AutoIncrement 属性设置为 true 的 DataColumn。不过,您将需要手动设置 AutoIncrementStep 和 AutoIncrementSeed 值。有关自动递增列的更多信息,请参阅创建 AutoIncrement 列。
当使用 FillSchema 或将 MissingSchemaAction 设置为 AddWithKey 时,将需要在数据源中进行额外的处理来确定主键列信息。这一额外的处理可能会降低性能。如果主键信息在设计时已知,为了实现最佳性能,建议显式指定一个或多个主键列。有关为表显式设置主键信息的信息,请参阅为表定义主键。

以下代码示例显示如何使用 FillSchema 向 DataSet 添加架构信息。

[Visual Basic]
Dim custDS As DataSet = New DataSet()

custDA.FillSchema(custDS, SchemaType.Source, "Customers")
custDA.Fill(custDS, "Customers")

[C#]
DataSet custDS = new DataSet();

custDA.FillSchema(custDS, SchemaType.Source, "Customers");
custDA.Fill(custDS, "Customers");

以下代码示例显示如何使用 Fill 方法的 MissingSchemaAction.AddWithKey 属性向 DataSet 添加架构信息。

[Visual Basic]
Dim custDS As DataSet = New DataSet()

custDA.MissingSchemaAction = MissingSchemaAction.AddWithKey
custDA.Fill(custDS, "Customers")

[C#]
DataSet custDS = new DataSet();

custDA.MissingSchemaAction = MissingSchemaAction.AddWithKey;
custDA.Fill(custDS, "Customers");

多个结果集

如果 DataAdapter 遇到从 SelectCommand 中返回的多个结果集,它将在 DataSet 中创建多个表。将向这些表提供递增的默认名称 TableN,以表示 Table0 的“Table”为第一个表名。如果以参数形式向 FillSchema 方法传递表名称,则将向这些表提供递增的默认名称 TableNameN,这些表名称以表示 TableName0 的“TableName”为起始。

注意 如果对返回多个结果集的命令调用 OleDbDataAdapter 对象的 FillSchema 方法,则将只返回第一个结果集中的架构信息。当使用 OleDbDataAdapter 为多个结果集返回架构信息时,建议在调用 Fill 方法时将 MissingSchemaAction 指定为 AddWithKey 并获取架构信息。

你遇到的问题是 C# 中使用 `DataSet` 或 `DataAdapter` 进行数据库操作时常见的异常: ``` 未能启用约束。一行或多行中包含违反非空、唯一或外键约束的值。 ``` 尽管你确认主键不重复,且**仅在首次页面加载时报错,再次查询不报错**,这通常说明数据本身没有明显问题,但 `DataTable` 的约束检查在填充数据时触发了异常。 --- ### ✅ 问题原因分析 这个错误发生在你调用 `DataTable.EnforceConstraints = true`(默认为 `true`)时,`DataSet` 尝试验证数据是否符合定义的约束(如主键唯一性、非空字段等),但在某些情况下会误报或因临时状态导致失败。 #### 常见原因包括: 1. **主键列存在 DBNull 或重复值(即使你看不到)** 2. **DataTable 已有架构(schema),但新数据与原有约束冲突** 3. **多次填充同一个 DataTable 而未清除旧数据或约束状态** 4. **自动生成的 DataSet 约束(如 UniqueConstraint)对 null 值敏感** 5. **第一次加载时从数据库读取的数据中有隐藏问题(如延迟加载、并发修改)** 而“第二次查询不报错”说明:第一次出错后可能清除了上下文,或者数据缓存/连接状态改变了行为。 --- ### ✅ 解决方案与代码示例 #### 方案一:**临时禁用约束检查,排查数据问题** ```csharp try { // 创建数据集 DataSet dataSet = new DataSet(); // 禁用约束(便于调试) dataSet.EnforceConstraints = false; // 使用 DataAdapter 填充数据 using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM YourTable", connectionString)) { adapter.Fill(dataSet, "YourTable"); } // 检查是否有重复主键(假设主键是 ID) var table = dataSet.Tables["YourTable"]; var duplicateKeys = table.AsEnumerable() .GroupBy(row => row.Field<int>("ID")) .Where(g => g.Count() > 1) .Select(g => g.Key); if (duplicateKeys.Any()) { throw new Exception($"发现重复主键: {string.Join(", ", duplicateKeys)}"); } // 检查是否有主键为 null var nullKeys = table.AsEnumerable() .Where(r => r["ID"] == DBNull.Value) .ToList(); if (nullKeys.Any()) { throw new Exception("存在主键为 NULL 的记录"); } // 手动设置主键(确保不是靠推断) table.PrimaryKey = new DataColumn[] { table.Columns["ID"] }; // 启用约束(此时应安全) dataSet.EnforceConstraints = true; } catch (Exception ex) { Console.WriteLine("约束启用失败: " + ex.Message); throw; } ``` > 🔍 关键点:先关闭约束 → 填充数据 → 主动检查重复和 null → 再开启约束。 --- #### 方案二:**使用 `MissingSchemaAction.AddWithKey` 明确指定主键** 如果你是从数据库自动推断 schema,可能会因为未正确识别主键而导致约束异常。 ```csharp using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM YourTable", connectionString)) { DataSet dataSet = new DataSet(); // 明确告诉适配器:添加表结构,并包含主键信息 adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; adapter.Fill(dataSet, "YourTable"); // 此时 PrimaryKey 应该已被正确设置 DataTable table = dataSet.Tables["YourTable"]; // 可选:打印主键列名用于调试 Console.WriteLine("主键列: " + string.Join(", ", table.PrimaryKey.Select(c => c.ColumnName))); // 最后再启用约束(其实默认已启用) dataSet.EnforceConstraints = true; // 如果前面没关,这里只是确认 } ``` > ⚠️ `MissingSchemaAction.AddWithKey` 是关键!它会让 `DataAdapter` 主动从数据库获取主键信息并设置 `DataTable.PrimaryKey`,避免后续约束冲突。 --- #### 方案三:**每次重新创建 DataTable,避免残留状态** 如果是在 Web 页面中反复查询(如 ASP.NET Web Forms),请确保不要重用旧的 `DataTable` 实例。 ❌ 错误做法(类级字段复用): ```csharp private DataTable sharedTable = new DataTable(); // ❌ 全局共享,容易累积脏数据 ``` ✅ 正确做法: ```csharp protected void Page_Load(object sender, EventArgs e) { DataTable table = new DataTable(); // ✅ 每次新建 using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM YourTable", connString)) { adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; adapter.Fill(table); } // 绑定到控件... } ``` --- ### 🧪 排查建议 1. **打印所有行的主键值**,查看是否有 `DBNull` 或不可见字符。 2. **检查数据库视图或查询是否包含 LEFT JOIN 导致主键重复?** 3. **是否使用了 UNION 或子查询导致同一 ID 出现多次?** 4. **第一次请求时是否存在并发写入?** 5. **使用 SQL Profiler 查看实际执行的 SQL 是否一致?** --- ### ✅ 总结 | 原因 | 解决方式 | |------|----------| | 架构未正确识别主键 | 使用 `MissingSchemaAction.AddWithKey` | | 数据中存在重复或 null 主键 | 查询前手动检查去重 | | 复用 DataTable 导致状态污染 | 每次新建 DataTable | | 约束提前启用 | 临时关闭 `EnforceConstraints` | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值