Главния недостатък на ползването на SQL stored procedures от С# код е това че трявба да напаснеш (match) прекалено много типове. И се налага да пишеш дълги wrapper functions. Ако ползваш stored procedures доста, знаеш колко е досадно да описваш всеки параметър: име, тип, размер и т.н. ... освен това ако промениш процедурата и пак трябва да ходиш да променяш кода в data service layer-a. Примерно имаш следната процедура:
CREATE PROCEDURE GetData @ID int
AS
SELECT * FROM DataTable WHERE [ID] = @ID
за да извикаш тази процедура обикновенно и правиш wrapper функция като тази:
Recordset GetData(Connection cn, int nID)
{
Command cm = new Command();
cm.ActiveConnection = cn;
cm.CommandText = "GetData";
cm.CommandType = adCmdStoredProc;
Parameters params = cm.Parameters;
params.Append(cm.CreateParameter("@RETURN_VALUE", adInteger, adParamReturnValue, 0);
params.Append(cm.CreateParameter("@ID", adInteger, adParamInput, 0);
params("@ID").Value = nID;
Recordset r = new Recordset();
r.CursorLocation = adUseClient;
r.Open ( cm, , adOpenStatic, adLockReadOnly );
r.ActiveConnection = null;
return r;
}
Този подход съвсем не е зле в ерата когато нямаше .NET, обаче сега като видиш половината ти екип през половината ден да пише такъв код и после половината му рипорт да е: "Емиии ... писах wrapper functions ..." и си викаш - отиде ми и производителност и deadlines и кво ли не ... и почваш да мъдриш некви гениални идеи ... примерно: да напишеш генератор който автоматично прави wrapper functions чрез SQLDMO ... или пък да правиш XML дефиниции на процедурите и после с XSLT (за което според някой хора в този форум аз изобщо не съм бил чувал) да генерираш кода на wrapper function-a.
Но първите идеи не е задължително да са винаги най-подходящите ...
Как reflection променя цялата тази картинка ???!!!
Лесно може да си представиш как чрез reflection може да обходиш параметрите на дадена функция и да създадеш SqlCommand обект с респективните SqlParameter обекти. Всичко което трябва да направиш е да дефинираш функции които да представляват твоите процедури и останалото ще се прави от една utility function която динамично ще създава и конфигурира SqlCommand обекти по време на изпълнение на програмата за да се обръща към базата данни. Примерно нещо такова:
sealed class SqlCommandGenerator
{
private SqlCommandGenerator() {}
public static SqlCommand GenerateCommand(SqlConnection connection,
MethodInfo method, object[] values)
{
SqlCommand command = new SqlCommand(method.Name, connection);
command.CommandType = CommandType.StoredProcedure;
ParameterInfo[] parameters = method.GetParameters();
for (int i = 1; i < parameters.Length; i++)
{
SqlParameter sqlParameter = new SqlParameter();
sqlParameter.ParameterName = "@" + parameters[ i ].Name;
sqlParameter.Value = values[ i ];
command.Parameters.Add(sqlParameter);
}
return command;
}
}
Като имаш обект от тип Connection, метаданните на метода и стойностите на параметрите GenerateCommand генерира и изпълнява SqlCommand обект все едно че си го писал като сорс. Даже няма нужда да да ползвам типа на параметъра да получа SqlDbType за да дам типа на SqlParametera, защото SqlParameter вече има такава функционалност като подаваш Value и го прави вместо тебе.
След това трябва да извикаш GenerateCommand от GetData и да използваш генерираната команда:
public static DataSet GetData(SqlConnection connection, int Id)
{
MethodInfo methodInfo =
typeof(myDatabase).GetMethod("GetData",
new Type[] { typeof(SqlConnection), typeof(int) });
SqlCommand command =
SqlCommandGenerator.GenerateCommand(connection,
methodInfo, new object[] { Id });
DataSet dataSet = new DataSet();
SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(dataSet);
return dataSet;
}
До тук добре ... обаче не е идеално защото пак ще ти се налага да правиш промени в кода ако се промени процедурата, примерно смениш името. Това лесно може да се реши ако се разходиш по стека и вземеш frame-а на въпросния метод. Наред с всички хубави неща в FCL, има еднa особено приятна функция, леко скрита ама това да не те притеснява, MehodBase.GetCurrentMethod. Тя ти връща MethodBase (MethodInfo или ConstructorInfo) за функцията която я вика. И става нещо такова:
public static DataSet GetData(SqlConnection connection, int Id)
{
SqlCommand command = SqlCommandGenerator.GenerateCommand
(connection, (MethodInfo) MethodBase.GetCurrentMethod(),
new object[] { Id });
DataSet dataSet = new DataSet();
SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(dataSet);
return dataSet;
}
горе долу това е идеята макар че има случаи когато имаш безименни параметри или пък типовете на параметрите са леко различни от тези на метода или пък NULL values ... ама и те си имат решение - Attributes ...
XM TurboCT .. where comfort meets power
|