Доброе время суток,
Я 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;
}
Вот в общем-то и все. Так я делаю работу с БД.
Думаю, что мой вопрос и поднятая тема очень важны, и будут очень полезны большому количеству начинающих и средних программистов.
Так что... Направьте на путь истинный?
Заранее большое спасибо. С уважением.