简体   繁体   中英

How can I refactor this method?

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();
        }  
}  

It's working correctly but I want to make it shorter so that it's easier to understand. How can I refactor it?

Disclaimer: as suggested by Darin, I changed his original solution a little bit. Doc Brown.

The fact that this code is large is the least problem. You have a far bigger problems with SQL injection here. You should use parametrized queries to avoid this.

So I would start with externalizing the data access logic into a separate method:

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();
    }
}

and then:

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

Using "tableName" and "attributeName" as dynamic parts of your SQL is no security problem as long as you don't let the user provide input for those two parameters.

You could proceed reusing this method for the other cases.

Statments like if (textBox4.Text == "" && textBox2.Text == "") means nothing at all, if you are not up to speed on what that particular part of the application is up to. In this case it seems as if they represent one value each, and that at least one of them should contain something for any operation to be legal. Studying the SQL statements suggests that textBox2 is a quantity and textBox4 is a price. First thing would be to change those control names into something more meaningful.

Second, I'd wrap the checks into methods with more descriptive names:

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

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

Then you can rewrite the if-block as so:

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();
}

This way you don't repeat the SQL queries in the code and it's fairly easy to read the code and understand what it does. The next step would be to rewrite the SQL queries to use parameters instead of concatenated strings (which opens your code for SQL injection attacks), as suggested by Darin .

create strings (or an array of strings) for each of your commands (inside of your if statements), if the strings are not null, then run your sql afterwards.

[note]
you don't close your connection in every case
[/note]

As well as making it easier to understand, you may want to refactor it so it's easier to test .

In this case that may entail getting rid of the ConnectionClass singletons (and instead use IoC and inject a connection factory) and the MessageBox (eg throw an exception and let the surrounding code determine how to display this message)

Making the code "small" may not be the best way to make it readable or "easy to understand". I find more terse code to be much more difficult to understand than code that is well laid out with good quality variable, method, property, class names etc.

I think the more serious issue is not the size of the code, but the fact that you are susceptible to SQL Injection attacks.

Here is an article I wrote way back that you might find useful: SQL Injection attacks and some tips on how to prevent them

I hope that helps.

Your code is SQL Injection waiting to happen. Watch out for 'ol Johnny Tables.

I have streamlined and optimised your code a bit, with a few comments.

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. You can immediately simplify your code by removing braces on conditional statements such as if if you are only executing single lines of code. I have done this for you.
  2. Don't build your SQL statements using string concatenation without first escaping any user inputted variables utilised. Ie, update myrecord set price='" + textbox4.Text + "' .. should be at the very least update myrecord set price='" + textbox4.Text.Replace("'", "''") .. to avoid possible SQL injection attacks.
  3. Instead of testing .Text == "" , it is generally preferred to use .string.IsNullOrEmpty() , or .string.IsNullOrWhitespace() on .NET 4 to cover both null and empty strings.
  4. Replace any explicitly typed data types for var .
  5. and finally, you could simplify this code by returning out as soon as validation is not met (as seen in first couple of lines) and also specifying one OpenConnection() and CloseConnection() at the top and bottom of this method after the validation has occurred.

Also a spelling mistake fixed: :)

You can use parameters, that you may set to NULL when you don't want to update a field:

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

Then you can one single command depending on the text fields with Command.Parameters.AddWithValue method. Use DBNull.Value for NULL . Even if you do not change the structure, you should do this to prevent SQL injection attacks.

Cheers, Matthias

You can see that

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

is reduntant, so why not move it after the last else if scope.

why don't you simply create one more method:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM