Оформление работы с БД в корпоративных приложениях - как?
От: Kazna4ey  
Дата: 22.09.07 10:08
Оценка: 4 (1) -1 :))) :))
Доброе время суток,

Я 2 года занимаюсь разработкой корпоративных (бизнес) приложений. Как вы знаете, очень большую часть в такого типа приложениях занимает работа с БД. И очень важно правильно ее оформить, потому что при разрастании проекта за несколько тысяч строк (по моему опыту примерно 10000 строк) появляются некоторые проблемы. Можно выделить 2 основные проблемы:

1) Изменение схемы БД. На каком-то уровне оно становится сложным или просто невозможным. Например мы хотим перенести какое-то поле в отдельную таблицу, а в оригинальном поле указывать ID строки из этой новой таблицы. Вполне реальная ситуация что это может понадобится. Но теперь мне приходится перелопачивать сотни запросов в программе и десятки хранимых процедур, а потом молиться что нигде не вывалится скрытый косяк.

2) Смена СУБД. No comments.

Понимаю, что способов оформления работы с БД может быть много. Это может быть тупое написание SQL-кода в обработчике кнопки, это может быть оформление отдельного класса, в котором этот код уже будет записан, это может быть тот же Query Object, и т.д. Вопрос в том какой метод наиболее эффективен и распространен при разработке корпоративных приложений среднего размера (под средним размером я понимаю пару десятков тысяч строк кода написанного вручную и БД из пары десятков таблиц)?

Опишу, как я оформляю работу с БД.

Если какое-то обращение к БД — единичное, т.е. запрос очень специфический и его вызов происходит только в одном месте программы, то я не отделяю его от кода формы, т.е. пишу его прямо в обработчике события или отдельным методом класса формы/контрола, например так:


    private void btnLink_Click(object sender, EventArgs e)
    {
        ...
        MySqlCommand command = new MySqlCommand("LinkSupPaymentToSupInvoice", conn);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add("?SupInvoiceID_", MySqlDbType.Int32);
                command.Parameters.Add("?SupTransactionsID_", MySqlDbType.Int32).Value = SupTransactionsID;
                for (int i = 0; i < SelectedRows.Length; i++)
                {
                    command.Parameters["?SupInvoiceID_"].Value = Convert.ToInt32(View.GetRowCellValue(SelectedRows[i], ID));
                    command.ExecuteNonQuery();
                }
        ...
    }


Пока писал пример, возник попутный вопрос:
Насколько нужно/ненужно использовать хранимые процедуры/функции? Лично мне это решение зачастую кажется более удобным по нескольким причинам: читабельность кода в программе (просто видим имя процедуры а не длинный SQL запрос и сразу все понимаем), простота изменения (если нужно внести изменения, зачастую изменяем только ХП на сервере а не изменяем что-то в нескольких местах в программе где она используется), ну и для меня не очень важный фактор — производительность.

Ладно, отвлекся, идем дальше.

Если запрос или его модификация должен вызываться из нескольких мест в программе, то я его оформляю в виде статического метода в классе типа CommonFuncs:

    public static double GetPartPrice(int ManufacturerID, int SupplierID, string PartN, MySqlConnection conn)
    {
        try
        {
            MySqlCommand command = new MySqlCommand("GetPartPrice", conn);
            command.CommandType = System.Data.CommandType.StoredProcedure;
            command.Parameters.Add("?ManufacturerID_", MySqlDbType.Int32).Value = ManufacturerID;
            command.Parameters.Add("?SupplierID_", MySqlDbType.Int32).Value = SupplierID;
            command.Parameters.Add("?PartN_", MySqlDbType.VarChar).Value = PartN;
            command.Parameters.Add("?Result", MySqlDbType.Double).Direction = System.Data.ParameterDirection.ReturnValue;
            command.ExecuteNonQuery();
            return Convert.ToDouble(command.Parameters["?Result"].Value);
        }
        catch (Exception ex)
        {
            common.ShowException("An error occurred while trying to get part price from the database.\n\n" +
            "Debug info:\ncommon.cs, GetPartPrice()\n", ex.Message);
            return 0;
        }
    }


Если работа происходит с таблицей (всмысле гридом) обычно стараюсь работать через SelectCommand, UpdateCommand и т.д. класса SqlDataAdapter, типа так:


    ...
    adapterNotScanned.SelectCommand = db.BarcodeSystem.CreateDataAdapterSelectCommand(conn);
    adapterNotScanned.SelectCommand.Parameters.Add("?IsScanned", MySqlDbType.Bit).Value = false;
    adapterNotScanned.SelectCommand.Parameters.Add("?IsPacked", MySqlDbType.Bit).Value = false;
    adapterNotScanned.UpdateCommand = db.BarcodeSystem.NotScannedUpdateCommand(conn);
    adapterNotScanned.TableMappings.Add("parts", "partsNotScanned");
    ...

    // В классе db.BarcodeSystem все выглядит типа так:
    public static MySqlCommand CreateDataAdapterSelectCommand(MySqlConnection conn)
    {
        string strSQL;
        strSQL = "SELECT blablabla FROM parts WHERE Customer = ?Customer AND OrderDebited = true AND " + 
        "OrderInvoiced = false AND IsScanned = ?IsScanned AND IsPacked = ?IsPacked ORDER BY ManufacturerID, PartN";
        MySqlCommand cmd = new MySqlCommand(strSQL, conn);
        return cmd;
    }


Вот в общем-то и все. Так я делаю работу с БД.
Думаю, что мой вопрос и поднятая тема очень важны, и будут очень полезны большому количеству начинающих и средних программистов.
Так что... Направьте на путь истинный?

Заранее большое спасибо. С уважением.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.