我该如何重构这个方法?

时间:2011-05-26 12:15:32

标签: c#

private void Update_Record_Click(object sender, EventArgs e)  
    {  
        ConnectionClass.OpenConnection();  

        if (textBox4.Text == "" && textBox2.Text == "")  
        {  
            MessageBox.Show("No value entred for update.");  
        }  
        else if (textBox4.Text != "" && textBox2.Text != "")  
        {  
            SqlCommand cmd = new SqlCommand("update medicinerecord set quantity='" + textBox2.Text + "' where productid='"+comboBox1.Text+"'", ConnectionClass.OpenConnection());  
            cmd.ExecuteNonQuery();  

            cmd = new SqlCommand("update myrecord set price='" + textBox4.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
            cmd.ExecuteNonQuery();
            ConnectionClass.CloseConnection();
        }
        else if (textBox2.Text != "")
        {
            SqlCommand cmd = new SqlCommand("update myrecord set quantity='" + textBox2.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
            cmd.ExecuteNonQuery();
            ConnectionClass.CloseConnection();
        }
        else if (textBox4.Text != "")
        {
            SqlCommand cmd = new SqlCommand("update myrecord set price='" + textBox4.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
            cmd.ExecuteNonQuery();
            ConnectionClass.CloseConnection();
        }  
}  

它工作正常但我想缩短它以便更容易理解。我怎么能重构它?

10 个答案:

答案 0 :(得分:6)

免责声明:正如Darin所说,我稍微改变了原来的解决方案。布朗博士。

此代码 large 的事实是最不重要的问题。这里有一个更大的SQL注入问题。您应该使用参数化查询来避免这种情况。

所以我首先将数据访问逻辑外部化为一个单独的方法:

public void UpdateMedicineRecordQuantity(string tableName, string attributeName, string productId, string attributeValue)
{
    using (var conn = new SqlConnection("YOUR ConnectionString HERE"))
    using (var cmd = conn.CreateCommand())
    {
        conn.Open();
        cmd.CommandText = "UPDATE " + tableName + "SET " + attributeName+ " = @attributeValue where productid = @productid";
        cmd.Parameters.AddWithValue("@attributeValue", attributeValue);
        cmd.Parameters.AddWithValue("@productid", productId);
        cmd.ExecuteNonQuery();
    }
}

然后:

string productId = comboBox1.Text;
string quantity = textBox2.Text;
UpdateMedicineRecordQuantity("medicinerecord", "quantity", productId, quantity);

使用“tableName”和“attributeName”作为SQL的动态部分是没有安全问题的,只要您不让用户为这两个参数提供输入。

您可以继续为其他情况重复使用此方法。

答案 1 :(得分:3)

if (textBox4.Text == "" && textBox2.Text == "")这样的语句根本没有任何意义,如果你不能快速了解应用程序的特定部分是什么。在这种情况下,似乎它们各自代表一个值,并且它们中的至少一个应包含某些操作合法的东西。研究SQL语句表明textBox2是一个数量而textBox4是一个价格。首先,将这些控件名称更改为更有意义的名称。

其次,我将检查包装到具有更具描述性名称的方法中:

private bool HasPriceValue()
{
    return !string.IsNullOrEmpty(textBox4.Text);
}

private bool HasQuantityValue()
{
    return !string.IsNullOrEmpty(textBox2.Text);
}

然后你可以重写if-block:

if (!HasPriceValue() && !HasQuantityValue())
{ 
    MessageBox.Show("No value entred for update."); 
}
else
{
    ConnectionClass.OpenConnection(); 
    if (HasQuantityValue())  
    {  
        SqlCommand cmd = new SqlCommand("update medicinerecord set quantity='" + textBox2.Text + "' where productid='"+comboBox1.Text+"'", ConnectionClass.OpenConnection());  
        cmd.ExecuteNonQuery();  
    }
    if (HasPriceValue())
    {
        SqlCommand cmd = new SqlCommand("update myrecord set price='" + textBox4.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
        cmd.ExecuteNonQuery();
    }
    ConnectionClass.CloseConnection();
}

这样您就不会在代码中重复SQL查询,并且很容易阅读代码并理解它的作用。下一步是重写SQL查询以使用参数而不是连接字符串(这会打开SQL注入攻击的代码),as suggested by Darin

答案 2 :(得分:1)

为每个命令(在if语句中)创建字符串(或字符串数​​组),如果字符串不为null,则之后运行sql。

[注]
您不会在任何情况下关闭连接
[/注]

答案 3 :(得分:1)

除了让它更容易理解之外,你可能想要重构它以便更容易测试

在这种情况下,可能需要摆脱ConnectionClass单例(而是使用IoC并注入连接工厂)和MessageBox(例如抛出异常并让周围的代码确定如何显示此消息)

答案 4 :(得分:1)

使代码“小”可能不是使其可读或“易于理解”的最佳方式。我发现更多简洁的代码要比使用高质量变量,方法,属性,类名等精心布置的代码更难理解。

我认为更严重的问题不是代码的大小,而是您易受SQL注入攻击的事实。

这是我写回来的一篇文章,你可能觉得它很有用:SQL Injection attacks and some tips on how to prevent them

我希望有所帮助。

答案 5 :(得分:1)

您的代码是等待发生的SQL注入。注意'ol Johnny Tables。

答案 6 :(得分:1)

我对您的代码进行了简化和优化,并提供了一些评论。

private void Update_Record_Click(object sender, EventArgs e)  
{  
    if (textBox4.Text == "" && textBox2.Text == "")
    {
        MessageBox.Show("No value entered for update.");  
        return;
    }
    ConnectionClass.OpenConnection();  

    var cmd = new SqlCommand("update medicinerecord set quantity='" + textBox2.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
    cmd.ExecuteNonQuery();  

    cmd = null;
    if (textBox2.Text != "")
        cmd = new SqlCommand("update myrecord set quantity='" + textBox2.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());
    else if (textBox4.Text != "")
        cmd = new SqlCommand("update myrecord set price='" + textBox4.Text + "' where productid='" + comboBox1.Text + "'", ConnectionClass.OpenConnection());

    if (cmd != null) cmd.ExecuteNonQuery();
    ConnectionClass.CloseConnection();
}  
  1. 如果您只执行单行代码,则可以通过删除条件语句(如if)上的大括号来立即简化代码。我已经为你做了这件事。
  2. 不使用字符串连接构建SQL语句,而不首先转义任何用户输入的变量。也就是说,update myrecord set price='" + textbox4.Text + "' ..应该至少update myrecord set price='" + textbox4.Text.Replace("'", "''") ..以避免可能的SQL injection攻击。
  3. 而不是测试.Text == "",通常首选在.NET 4上使用!string.IsNullOrEmpty()!string.IsNullOrWhitespace()来覆盖空字符串和空字符串。
  4. 替换var
  5. 的所有显式类型数据类型
  6. 最后,您可以通过在不满足验证后立即返回(如前几行所示)并在顶部和底部指定一个OpenConnection()CloseConnection()来简化此代码验证发生后,此方法。
  7. 还修复了拼写错误! :)

答案 7 :(得分:0)

您可以使用当您不想更新字段时可以设置为NULL的参数:

UPDATE myrecord SET price = ISNULL(@price, price) -- MSSQL

然后,您可以使用Command.Parameters.AddWithValue方法根据文本字段使用一个命令。对DBNull.Value使用NULL。即使您不更改结构,也应该这样做以防止SQL注入攻击。

干杯,马蒂亚斯

答案 8 :(得分:0)

你可以看到

 cmd.ExecuteNonQuery();                 
    ConnectionClass.CloseConnection();  

是多余的,所以为什么不在最后一个else if范围之后移动它。

答案 9 :(得分:0)

为什么不简单地再创建一个方法:

void ExecuteCommand(string sql)
{
           SqlCommand cmd = new SqlCommand(sql);  
            cmd.ExecuteNonQuery();
            ConnectionClass.CloseConnection();
}