You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 01:35:26