从CSV取数并替换外键列数据报错求助:SqlParameter未找到
问题解决与优化方案
一、报错原因与修复
1. 直接报错原因
你的代码参数添加逻辑完全错误:SQL语句中明确使用了@FKTO、@Bezeichnung两个参数,但你实际添加的是@tblFkto.IdFKTO、@blGeraet.Id_Geraet——这两个是数据库表的列名,根本不属于参数范畴,所以执行时系统找不到@FKTO参数,触发报错。
2. SQL语句逻辑错误
INSERT语句里直接写tblFkto.IdFKTO、tblGeraet.Id_Geraet是无效的,因为这两个表不在INSERT的执行上下文里。必须通过传入的@FKTO、@Bezeichnung先查询出对应的外键ID,再插入到目标表。
3. 修复后的完整代码
private void insertInTab() { try { const string strQueryRechnung = @"IF NOT EXISTS ( SELECT r.Menge, r.Einheit, r.BetragBrutto, r.Beginn, r.Ende FROM tblRechnung r JOIN tblFkto f ON r.IdFKTO = f.IdFKTO JOIN tblGeraet g ON r.IdGeraet = g.Id_Geraet WHERE f.FKTO = @FKTO AND g.Bezeichnung = @Bezeichnung AND r.Menge = @Menge AND r.Einheit = @Einheit AND r.BetragBrutto = @BetragBrutto AND r.Beginn = @Beginn AND r.Ende = @Ende) INSERT INTO tblRechnung (Menge, Einheit, BetragBrutto, Beginn, Ende, IdFKTO, IdGeraet) VALUES (@Menge, @Einheit, @BetragBrutto, @Beginn, @Ende, (SELECT IdFKTO FROM tblFkto WHERE FKTO = @FKTO), (SELECT Id_Geraet FROM tblGeraet WHERE Bezeichnung = @Bezeichnung));"; // 连接和命令都放入using块,自动释放资源,无需手动Close using (var sqlConnection = new SqlConnection(ConnectionMSSQLServer)) using (var sqlCommand = new SqlCommand(strQueryRechnung, sqlConnection)) { // 添加SQL中实际用到的参数,类型根据数据库实际情况调整 sqlCommand.Parameters.Add("@FKTO", SqlDbType.NVarChar); sqlCommand.Parameters.Add("@Bezeichnung", SqlDbType.NVarChar); sqlCommand.Parameters.Add("@Menge", SqlDbType.Int); sqlCommand.Parameters.Add("@Einheit", SqlDbType.NVarChar, 50); sqlCommand.Parameters.Add("@BetragBrutto", SqlDbType.SmallMoney); sqlCommand.Parameters.Add("@Beginn", SqlDbType.DateTime); sqlCommand.Parameters.Add("@Ende", SqlDbType.DateTime); sqlConnection.Open(); for (int i = 2; i < dgvCSVRechnung.Rows.Count; i++) { var row = dgvCSVRechnung.Rows[i]; // 增加空值判断,避免空引用或类型转换报错 sqlCommand.Parameters["@FKTO"].Value = row.Cells[0].Value ?? DBNull.Value; sqlCommand.Parameters["@Bezeichnung"].Value = row.Cells[2].Value ?? DBNull.Value; sqlCommand.Parameters["@Menge"].Value = !string.IsNullOrEmpty(row.Cells[3].Value?.ToString()) ? Int32.Parse(row.Cells[3].Value.ToString()) : DBNull.Value; sqlCommand.Parameters["@Einheit"].Value = row.Cells[4].Value ?? DBNull.Value; sqlCommand.Parameters["@BetragBrutto"].Value = !string.IsNullOrEmpty(row.Cells[5].Value?.ToString()) ? Decimal.Parse(row.Cells[5].Value.ToString()) : DBNull.Value; sqlCommand.Parameters["@Beginn"].Value = DateTime.ParseExact(row.Cells[6].Value.ToString(), "dd.MM.yyyy", System.Globalization.CultureInfo.InvariantCulture); sqlCommand.Parameters["@Ende"].Value = DateTime.ParseExact(row.Cells[7].Value.ToString(), "dd.MM.yyyy", System.Globalization.CultureInfo.InvariantCulture); sqlCommand.ExecuteNonQuery(); } } } catch (Exception ex) { MessageBox.Show(ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
修复要点:
- 移除无效参数,添加SQL中实际使用的
@FKTO、@Bezeichnung - 用子查询获取外键ID,优化NOT EXISTS的查询逻辑(用JOIN替代旧的逗号连接表,避免笛卡尔积)
- 增加空值判断,防止空引用或类型转换错误
- 将SqlConnection放入using块,自动释放连接资源
二、CSV数据处理与外键替换的优化方案
1. 高效读取CSV
直接从DataGridView循环读取效率低且易出错,建议用CsvHelper库直接读取CSV文件到实体类:
// 定义实体类对应CSV列结构 public class RechnungCsvItem { public string FKTO { get; set; } public string Bezeichnung { get; set; } public int Menge { get; set; } public string Einheit { get; set; } public decimal BetragBrutto { get; set; } public DateTime Beginn { get; set; } public DateTime Ende { get; set; } } // 读取CSV的方法 private List<RechnungCsvItem> ReadCsv(string filePath) { using var reader = new StreamReader(filePath); using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); // 若CSV列名与实体属性名不一致,可配置映射规则 return csv.GetRecords<RechnungCsvItem>().Skip(2).ToList(); // 跳过前2行表头 }
2. 批量处理外键映射
提前批量查询所有需要的外键ID,避免循环中频繁访问数据库,大幅降低IO开销:
// 读取CSV数据 var csvItems = ReadCsv("你的CSV文件路径"); // 提取所有唯一的FKTO和Bezeichnung var fktoList = csvItems.Select(x => x.FKTO).Distinct().ToList(); var bezeichnungList = csvItems.Select(x => x.Bezeichnung).Distinct().ToList(); // 批量查询FKTO对应的IdFKTO,存入字典做映射 Dictionary<string, int> fktoMap = new Dictionary<string, int>(); using (var conn = new SqlConnection(ConnectionMSSQLServer)) { conn.Open(); // 用表值参数更安全,这里简化用字符串拼接(注意避免SQL注入) var fktoParams = string.Join(",", fktoList.Select((x, i) => $"@FKTO{i}")); var cmd = new SqlCommand($"SELECT FKTO, IdFKTO FROM tblFkto WHERE FKTO IN ({fktoParams})", conn); for (int i = 0; i < fktoList.Count; i++) { cmd.Parameters.AddWithValue($"@FKTO{i}", fktoList[i]); } using var reader = cmd.ExecuteReader(); while (reader.Read()) { fktoMap.Add(reader.GetString(0), reader.GetInt32(1)); } } // 同理批量查询Bezeichnung对应的Id_Geraet Dictionary<string, int> geraetMap = new Dictionary<string, int>(); // 代码逻辑与上面的fktoMap查询一致 // 用SqlBulkCopy批量插入,性能远高于循环ExecuteNonQuery DataTable dt = new DataTable(); dt.Columns.Add("Menge", typeof(int)); dt.Columns.Add("Einheit", typeof(string)); dt.Columns.Add("BetragBrutto", typeof(decimal)); dt.Columns.Add("Beginn", typeof(DateTime)); dt.Columns.Add("Ende", typeof(DateTime)); dt.Columns.Add("IdFKTO", typeof(int)); dt.Columns.Add("IdGeraet", typeof(int)); foreach (var item in csvItems) { // 检查外键是否存在,避免插入失败 if (fktoMap.ContainsKey(item.FKTO) && geraetMap.ContainsKey(item.Bezeichnung)) { dt.Rows.Add(item.Menge, item.Einheit, item.BetragBrutto, item.Beginn, item.Ende, fktoMap[item.FKTO], geraetMap[item.Bezeichnung]); } } // 执行批量插入 using (var conn = new SqlConnection(ConnectionMSSQLServer)) { conn.Open(); using var bulkCopy = new SqlBulkCopy(conn); bulkCopy.DestinationTableName = "tblRechnung"; // 若DataTable列名与数据库列名一致,自动映射,否则手动配置ColumnMappings bulkCopy.WriteToServer(dt); }
优化要点:
- 用CsvHelper直接读取CSV到实体,代码更简洁、效率更高
- 批量查询外键映射,减少数据库访问次数
- 用SqlBulkCopy批量插入,适合大量数据场景,性能提升明显
- 提前校验外键存在性,避免插入失败
内容的提问来源于stack exchange,提问作者Alexander Sutjagin
相关产品推荐
相关产品推荐

