【问题标题】:Invalid attempt to call read when reader is closed when inserting data插入数据时关闭阅读器时尝试调用读取无效
【发布时间】:2017-11-21 04:31:11
【问题描述】:

我有一个按钮,单击该按钮可将文本框和组合框字段中的数据插入数据库表中,但每次插入时都会给我“关闭阅读器时调用读取的无效尝试”。我怎样才能摆脱这个错误。欢迎使用优化代码的技巧,因为我知道我是个菜鸟。谢谢

private void btnSave_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            SqlConnection sqlCon = new SqlConnection(@"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
            sqlCon.Open();
            string Query1 = "insert into location(Storage, Shelf, columns, rows) values(" + txtWarehouse.Text + ", " + txtShelf.Text + ", " + txtColumn.Text + ", " + txtRow.Text + ")";
            SqlCommand sqlCmd = new SqlCommand(Query1, sqlCon);
            SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
            dataAdp.SelectCommand.ExecuteNonQuery();
            sqlCon.Close();

        }
        catch (Exception er)
        {
            MessageBox.Show(er.Message);
        }
        try
        {
            SqlConnection sqlCon = new SqlConnection(@"Data Source=(localdb)\mssqllocaldb; Initial Catalog=Storagedb;");
            sqlCon.Open();
            string Query3 = "SELECT LOCATION_ID FROM LOCATION WHERE storage='" + txtWarehouse.Text + "' AND shelf='" + txtShelf.Text + "' AND columns='"
                + txtColumn.Text + "' AND rows='" + txtRow.Text + "'";
            SqlCommand sqlCmd1 = new SqlCommand(Query3, sqlCon);
            SqlDataReader dr = sqlCmd1.ExecuteReader(); ;
            while (dr.Read())
            {
                string LocationId = dr[0].ToString();
                dr.Close();
                string Query2 = "insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID) values ('" + txtSku.Text + "','" + txtNimetus.Text + "', '"
                + txtMin.Text + "', '" + txtMax.Text + "', '" + txtQuan.Text + "', '" + LocationId + "', '" + (cbCat.SelectedIndex+1) + "', '" + (cbMail.SelectedIndex+1) + "')";
                SqlCommand sqlCmd = new SqlCommand(Query2, sqlCon);
                SqlDataAdapter dataAdp = new SqlDataAdapter(sqlCmd);
                dataAdp.SelectCommand.ExecuteNonQuery();
            }
            sqlCon.Close();
        }
        catch (Exception ed)
        {
            MessageBox.Show(ed.Message);
        }
    }

【问题讨论】:

  • 这里不需要DataAdapter。只需从您构建的 SqlCommand 调用 ExecuteNonQuery。
  • 删除 dr.Close();行
  • “已经有一个打开的数据读取器与此命令关联,必须先关闭”是我现在得到的错误,现在没有数据插入数据库
  • 您应该考虑更改处理 SQL 查询的方式。在您编写它时,它极易受到 SQL 注入攻击,任何使用您的应用程序的人都可以通过在文本框中输入脚本将整个数据库丢给您。查看 Paul 的回答 here,了解如何使用数据库参数来防止 SQL 注入。他还提供了一个很好的链接,可以帮助您更好地理解 SQL 注入。
  • 您正在尝试在 while 循环中读取,但在您调用 dr.Close() 的 while 循环中的第二行尝试使用调试器并开始审查您自己的代码。我还建议将选择并更新到自己的较小方法中,以使您的代码更具可读性。这有点混乱,并且在 SQL Injection 上也可以阅读

标签: c# sql sql-server wpf


【解决方案1】:

让我们尝试对您的代码进行一些调整。

  • 首先要考虑的是使用参数化查询,而不是 构建 sql 命令时的字符串连接。这是强制性的 避免解析错误和Sql Injections
  • 其次,您应该将一次性对象封装在using statement 确保他们在您完成后得到适当的处置 使用它们。
  • 第三,您可以从表中获取 LOCATION_ID,而无需运行 单独的查询只需将SELECT SCOPE_IDENTITY() 作为第二批添加到您的第一个命令。 (这仅在您将第一个表中的 LOCATION_ID 字段声明为 IDENTITY 列时才有效)
  • 第四,你把所有东西都放在transaction中,以免万一出现问题 部分代码意外失败

所以:

SqlTransaction tr = null;
try
{
    string cmdText = @"insert into location(Storage, Shelf, columns, rows) 
                       values(@storage,@shelf,@columns,@rows);
                       select scope_identity()"; 
    using(SqlConnection sqlCon = new SqlConnection(.....))
    using(SqlCommand cmd = new SqlCommand(cmdText, sqlCon))
    {
         sqlCon.Open();
         using( tr = sqlCon.BeginTransaction())
         {
             // Prepare all the parameters required by the command
             cmd.Parameters.Add("@storage", SqlDbType.Int).Value = Convert.ToInt32(txtWarehouse.Text);
             cmd.Parameters.Add("@shelf", SqlDbType.Int).Value = Convert.ToInt32(txtShelf.Text);
             cmd.Parameters.Add("@columns", SqlDbType.Int).Value = Convert.ToInt32(txtColumn.Text );
             cmd.Parameters.Add("@rows", SqlDbType.Int).Value = Convert.ToInt32(txtRow.Text);

             // Execute the command and get back the result of SCOPE_IDENTITY
             int newLocation = Convert.ToInt32(cmd.ExecuteScalar());

             // Set the second command text
             cmdText = @"insert into product(SKU, nimetus, minimum, maximum, quantity,location_ID,category_ID,OrderMail_ID) 
                         values (@sku, @nimetus,@min,@max,@qty,@locid,@catid,@ordid)";

              // Build a new command with the second text
              using(SqlCommand cmd1 = new SqlCommand(cmdText, sqlCon))
              {
                  // Inform the new command we are inside a transaction
                  cmd1.Transaction = tr;

                  // Add all the required parameters for the second command
                  cmd1.Parameters.Add("@sku", SqlDbType.NVarChar).Value = txtSku.Text;
                  cmd1.Parameters.Add("@nimetus",SqlDbType.NVarChar).Value = txtNimetus.Text;
                  cmd1.Parameters.Add("@locid", SqlDbType.Int).Value = newLocation;
                  .... and so on for the other parameters required

                  cmd1.ExecuteNonQuery();
                  // If we reach this point the everything is allright and
                  // we can commit the two inserts together
                  tr.Commit();
             }
        }
    }
}
catch (Exception er)
{
    // In case of exceptions do not insert anything...
    if(tr != null) 
       tr.Rollback();
    MessageBox.Show(er.Message);
}

请注意,在第一个命令中,我使用 SqlDbType.Int 类型的参数,因为您没有在文本周围使用单引号。这应该根据表列的真实数据类型进行验证,并进行调整以匹配类型。对于将所有内容作为文本放置的第二个命令也是如此,尽管其中一些字段似乎是整数(_location_id_ 可能是整数)。请对照您的表格进行验证。

【讨论】:

  • 谢谢,但是当我完成代码时,我在用第二个文本构建新命令的那一行得到“无法分配给 'cmd' 因为它是一个'使用变量”
  • 当我将“cmd”重命名为“SqlCommand cmd1”并将所有必需参数添加到“cmd1”时,我得到“当分配给命令的连接处于待处理的本地事务。该命令的 Transaction 属性尚未初始化"
  • 是的,你对第一个问题是正确的。我们需要构建一个新的 SqlCommand 并通知它我们在一个事务中。答案已更新
猜你喜欢
  • 2011-08-26
  • 1970-01-01
  • 2012-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多