Оптимизация метода - C # - PullRequest
2 голосов
/ 02 апреля 2011

Я разработал метод, который позволяет мне передавать таблицу (строку), массив столбцов (строку) и массив значений (объект) через параметры, которые я затем использую для создания параметризованного запроса.Хотя он прекрасно работает как по длине кода, так и по нескольким циклам for, он выделяет запах кода, в частности, я чувствую, что метод, который я использую для вставки запятой между столбцами и значениями, можно сделать другим и лучшим способом.

public static int Insert(string source, string[] column, object[] values)
{
    int rowsAffected = 0;
    try
    {
        using (SQLiteConnection conn = new SQLiteConnection(connectionString))
        {
            StringBuilder query = new StringBuilder();
            query.Append(" INSERT INTO ");
            query.Append(source);
            query.Append("(");

            for (int i = 0; i < column.Length; i++)
            {
                query.Append(column[i]);

                if (i < values.Length - 1)
                {
                    query.Append(",");
                }
            }

            query.Append(")");
            query.Append(" VALUES ");
            query.Append("(");

            for (int i = 0; i < values.Length; i++)
            {
                query.Append("@" + values[i].ToString());

                if (i < values.Length - 1)
                {
                    query.Append(",");
                }
            }

            query.Append(")");

            conn.Open();
            using (SQLiteCommand cmd = new SQLiteCommand(query.ToString(), conn))
            {
                for (int i = 0; i < values.Length; i++)
                {
                    cmd.Parameters.AddWithValue("@" + values[i].ToString(), values[i]);
                }
                rowsAffected = cmd.ExecuteNonQuery();
            }
        }
        return rowsAffected;
    }
    catch (Exception e)
    {
        MessageBox.Show(e.Message);
    }
    return 0;
}

Я использую библиотеку System.Data.SQLite для взаимодействия с базой данных.

Спасибо за любые предложения!

Ответы [ 3 ]

6 голосов
/ 02 апреля 2011

Это мой идиоматический способ добавления нескольких значений с помощью разделителя с использованием StringBuilder:

string separator = ",";
for (int i = 0; i < column.Length; i++)
{
    query.Append(column[i]);
    query.Append(separator);
}
query.Length -= separator.Length;

Это предполагает, что у вас будет хотя бы одно значение, и обычно, где я его использую, это будет ошибкойиметь хотя бы одно значение (и, похоже, ваш сценарий таков).

Похоже, что вы оставили этот код открытым для SQL-инъекции.

Вы, кажется, пытаетесь использовать параметры, но я не думаю, что вы сделали это правильно.Как я читаю код, вы используете фактическое значение параметров вместо их индекса.Я бы предложил эту модификацию (предполагается, что ваш массив имен столбцов исходит из надежного источника, а ваши значения - нет):

        for (int i = 0; i < values.Length; i++)
        {
            query.Append("@" + i.ToString()); // instead of query.Append("@" + values[i].ToString());

            if (i < values.Length - 1)
            {
                query.Append(",");
            }
        }

        query.Append(")");

        conn.Open();
        using (SQLiteCommand cmd = new SQLiteCommand(query.ToString(), conn))
        {
            for (int i = 0; i < values.Length; i++)
            {
                cmd.Parameters.AddWithValue("@" + i.ToString(), values[i]); // instead of cmd.Parameters.AddWithValue("@" + values[i].ToString(), values[i]);
            }
            rowsAffected = cmd.ExecuteNonQuery();
        }
    }
3 голосов
/ 02 апреля 2011

Количество параметров и значений должно всегда быть одинаковым, чтобы это работало, поэтому вы можете устранить цикл, используя два StringBuilders. (Непроверенный код, но он должен понять смысл)

            StringBuilder query = new StringBuilder();
            StringBuilder insertParams = new StringBuilder();
            query.Append(" INSERT INTO ");
            query.Append(source);
            query.Append("(");

            for (int i = 0; i < column.Length; i++)
            {

                if (i < values.Length - 1)
                {
                    query.Append(",");
                    insertParams.Append(",");
                }
                query.Append(column[i]);
                insertParams.Append("@" + values[i].ToString());
            }

            query.Append(")");
            query.Append(" VALUES ");
            query.Append("(");
            query.Append(insertValues.ToString());
            query.Append(")");

Поскольку длины одинаковы, вы можете построить список параметров и список значений одновременно, а затем просто вставить список значений в соответствующее место в конце цикла. Чистый результат должен быть быстрее. :)

1 голос
/ 02 апреля 2011

Вот еще один вариант. На самом деле делает то же самое, что делает ваш оригинальный код, но разбивает его на более мелкие куски и абстрагирует его в класс с именем InsertBuilder.

public class InsertBuilder
{
    public InsertBuilder()
    {
    }

    public InsertBuilder(string tableName, string[] columns, object[] values)
    {
        this.tableName = tableName;
        this.columns = columns;
        this.values = values;
    }

    private string tableName;
    public string TableName
    {
        get { return tableName; }
        set { tableName = value; }
    }

    private string[] columns;
    public string[] Columns
    {
        get { return columns; }
        set { columns = value; }
    }


    private object[] values;
    public object[] Values
    {
        get { return values; }
        set { values = value; }
    }

    public string InsertString
    {
        get
        {
            return CreateInsertString();
        }
    }

    public void Clear()
    {
        this.values = null;
        this.columns = null;
        this.tableName = null;
    }

    private string CreateInsertString()
    {
        if(columns.Length == 0) 
            throw new InvalidOperationException(
                "Columns must contain atleast one column"
                );

        if(values.Length == 0) 
            throw new InvalidOperationException(
                "Values must contain atleast one value"
                );

        if(columns.Length != values.Length)
        {
            throw new InvalidOperationException(
                string.Format(
                    "Columns length {0} does not match Values length {1}",
                    columns.Length,
                    values.Length)
                    );
        }

        StringBuilder insertString = new StringBuilder();

        insertString.Append(CreateTableStatement());

        insertString.Append(CreateColumnsStatement());

        insertString.Append(CreateValuesStatement());

        return insertString.ToString();

    }

    private string CreateTableStatement()
    {
        return " INSERT INTO " + tableName;
    }

    private string CreateColumnsStatement()
    {
        StringBuilder columnsStatement = new StringBuilder();

        columnsStatement.Append("(");

        for(int i = 0;i < columnsStatement.Length;i++)
        {
            columnsStatement.Append(columnsStatement[i]);
            if(i < values.Length - 1) { columnsStatement.Append(","); }
        }

        columnsStatement.Append(")");

        return columnsStatement.ToString();
    }

    private string CreateValuesStatement()
    {
        StringBuilder valuesStatement = new StringBuilder();

        valuesStatement.Append("VALUES");
        valuesStatement.Append("(");

        for(int i = 0;i < values.Length;i++)
        {
            valuesStatement.Append("@" + values[i].ToString());

            if(i < values.Length - 1) { valuesStatement.Append(","); }
        }

        valuesStatement.Append(")");

        return valuesStatement.ToString();
    }

}

Тогда ваш исходный код выглядит примерно так.

public static int Insert(string source, string[] column, object[] values)
{
    int rowsAffected = 0;
    try
    {
        using(SQLiteConnection conn = new SQLiteConnection(connectionString))
        {
            InsertBuilder insertBuilder = new InsertBuilder();
            insertBuilder.TableName = source;
            insertBuilder.Columns = column;
            insertBuilder.Values = values;

            using(SQLiteCommand cmd = new SQLiteCommand(insertBuilder.InsertString, conn))
            {
                for(int i = 0;i < values.Length;i++)
                {
                    cmd.Parameters.AddWithValue("@" + values[i].ToString(), values[i]);
                }

                conn.Open();

                rowsAffected = cmd.ExecuteNonQuery();
            }
        }

        return rowsAffected;
    }
    catch(Exception e)
    {
        MessageBox.Show(e.Message);
    }

    return 0;
}
...