Шифрование строки подключения. Data Protection API
Один из вариантов защитить строку подключения в своем конфигурационном файле - это воспользоваться Data Protection API (DAPI). Начиная с Windows 2000, DAPI является частью операционной системы.
Допустим, нам необходимо зашифровать данные, хранящиеся в секции connectionStrings. Для этого мы воспользуемся классом DataProtectionConfigurationProvider:
Подключим к нашему проекту сборку System.Configuration.dll и используем следующий код:
public void DataProtectionAPITest() { try { //открываем секцию connectionStrings из App.config Configuration config = ConfigurationManager.OpenExeConfiguration( System.Reflection.Assembly.GetExecutingAssembly().Location);
ConnectionStringsSection section = config.GetSection("connectionStrings") as ConnectionStringsSection;
if (!section.SectionInformation.IsProtected) { // выполняем шифрование секции section.SectionInformation.ProtectSection( "DataProtectionConfigurationProvider"); // Сохраняем конфигурацию config.Save(); }
} catch (Exception ex) { Console.WriteLine(ex.Message); }
// получаем строку подключения из зашифрованной секции Console.WriteLine(Properties.Settings.Default.ConnectionString); }
ПРЕДУПРЕЖДЕНИЕ. Данные,
хранящиеся в секции, могут быть расшифрованы только на том компьютере,
на котором были зашифрованы. Таким образом, процедуру шифрования данных
необходимо вызывать на компьютере конечного пользователя. |
Данный пример как раз подходит для этих целей. При установке
приложения мы помещаем в папку с программой ещё не зашифрованный
App.Config. При первом запуске приложения данная процедура его
зашифрует и, в последствии, программа будет работать уже с защищенной
секцией. Можно так же вызывать процедуру шифрования во время установки
приложения.
К сожалению, нет стандартных средств для защиты UDL файлов,
поэтому стоит воспользоваться способом хранения защищенных строк
подключения в файле конфигурации.
Команды
Команды предназначены для передачи запросов базе данных. Для Ole Db
провайдеров команда реализуется классом OleDbCommand. Команда всегда
выполняется для заданного открытого подключения к базе данных в
контексте транзакции. Для того чтобы выполнить запрос к базе данных необходимо выполнить следующую последовательность действий:
- Создать подключение к БД и открыть его
- Создать активную транзакцию из текущего подключения – метод OleDbConnection.BeginTransaction()
- Создать объект OleDbCommand, либо используя один из вариантов
перегруженного конструктора, либо метод OleDbConnection.CreateCommand()
- Установить свойство команды Transaction, если оно не было задано в конструкторе
- Задать текст команды CommandText
- Для обращения к базе данных у команды есть три метода ExecuteScalar, ExecuteReader и ExecuteNonQuery.
- Завершить транзакцию OleDbTransaction.Commit() или откатить OleDbTransaction.Rollback() и закрыть подключение.
ExecuteScalar
Возвращает единственное значение первой колонки первой строки.
Остальные результаты игнорируются. Этот метод полезен для запросов,
которые, к примеру, считают количество записей в таблице –
соответственно возвращают только одно значение:
public void ExecuteScalarTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
OleDbCommand cmd = new OleDbCommand("select count(*) from employee", con, trans); Console.WriteLine("Record count:" + cmd.ExecuteScalar().ToString()); trans.Commit(); con.Close(); }
ExecuteReader
Данный метод возвращает объект OleDbDataReader, который по своему
назначению очень близок объекту Recordset из классического ADO. Он
использует однонаправленное ForwardOnly чтение данных, реализуя
подсоединенную модель доступа. Таким образом, при его использовании
необходимо наличие открытого подключения к базе.
Навигация по строкам результирующего множества осуществляется при помощи метода Read(), который возвращает true в случае, если ещё остались строки и false
в противном случае. При вызове метода команды ExecuteReader(),
созданный им объект OleDbDataReader не спозиционирован на первой строке
результирующего множества и для её прочтения необходимо сначала вызвать
метод Read().
Наиболее удобным способом чтения данных из результирующего
множества является использование метода Read() совместно с конструкцией
while:
public void ExecuteReaderTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); //Испольуем метод CreateCommand для создания команды OleDbCommand cmd = con.CreateCommand(); cmd.Transaction = con.BeginTransaction(); cmd.CommandText = "select * from employee"; OleDbDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
//чтение данных while (rdr.Read()) { string tmp =""; for(int i=0; i<rdr.FieldCount -1;i++) { if (tmp != "") tmp += "\t"; tmp += rdr[i].ToString(); }
Console.WriteLine(tmp); }
rdr.Close(); //после вызова OleDbDataReader.Close() подключение к БД будет закрыто Assert.AreEqual(ConnectionState.Closed,con.State); }
ПРИМЕЧАНИЕ. Обратите внимание, что после вызова метода OleDbDataReader.Close() подключение к базе данных будет закрыто. Это произошло потому, что я применил перегруженный метод ExecuteReader() с заданным параметром CommandBehavior.CloseConnection. По умолчанию после выполнения метода OleDbDataReader.Close() подключение к базе данных остается открытым |
ExecuteNonQuery
Метод применяется для выполнения запросов, которые возвращают количество обработанных записей, таких как insert, update, delete, а так же для выполнения хранимых процедур, результат которых помещается в OUT параметры команды:
public void ExecuteNonQueryTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
//INSERT OleDbCommand cmd = new OleDbCommand( "insert into country (country,currency) values(:country,:currency) ", con, trans); cmd.Parameters.AddWithValue("country", "Russia"); cmd.Parameters.AddWithValue("currency", "Kopec"); // количество обработанных строк Assert.AreEqual(1, cmd.ExecuteNonQuery()); //UPDATE cmd = new OleDbCommand( "update country set currency=:currency where country =:country", con, trans); cmd.Parameters.AddWithValue("currency", "Rouble"); cmd.Parameters.AddWithValue("country", "Russia");
// количество обработанных строк Assert.AreEqual(1, cmd.ExecuteNonQuery()); //DELETE cmd = new OleDbCommand( "delete from country where country =:country", con, trans); cmd.Parameters.AddWithValue("country", "Russia");
// количество обработанных строк Assert.AreEqual(1, cmd.ExecuteNonQuery());
trans.Commit(); con.Close(); }
Параметры команд
В большинстве случаев при выполнении команды требуется задать её параметры. Параметры добавляются в коллекцию Parameters. Они могут быть именованные и позиционные. Пример команды с позиционными параметрами:
insert into country (country,currency) values(?,?)
С именованными:
insert into country (country,currency) values(:country,:currency)
IBProvider сам умеет формировать список параметров, производя анализ
SQL выражения. Но, к сожалению, в ADO .Net необходимо вручную добавлять
эти параметры, т.к. команда не запрашивает их описание у Ole Db
провайдера. Если вспомнить ADO, то в нем список параметров прекрасно
формировался без необходимости вмешиваться в этот участок кода.
Для того, чтобы добавить параметр, нужно воспользоваться:
- для добавления именованного параметра и значения - методом AddWithValue()
- для добавления как именованных, так и неименованных параметров - перегруженным методом Add()
Если не указан тип параметра, он будет добавлен с Ole Db типом VarWChar, что соответствует .Net типу string, что кажется разумным. Об этом не стоит беспокоиться, т.к. IBProvider корректно обрабатывает приведение любых типов Firebird.
Нельзя не сказать о существующих ограничениях при использовании именованных параметров совместно с OleDbCommand.
В MSDN написано, что именованные параметры поддерживаются только для
поставщиков данных MSSQL и Oracle, а для поставщиков данных Ole Db и
ODBC поддерживаются только позиционные параметры. Использовать
именованные параметры все же можно, но их добавление в коллекцию
Parameters необходимо осуществлять в том же порядке, в каком они
следуют в запросе. К примеру, если текст команды:
update country set currency=:currency where country =:country
то сначала необходимо добавить параметр currency, а потом country:
cmd.Parameters.AddWithValue("currency", "Rouble"); cmd.Parameters.AddWithValue("country", "Russia");
Задавать значения параметров можно уже в произвольном порядке:
cmd.Parameters["country"].Value = "Latvia"; cmd.Parameters["currency"].Value = "Lat";
Вызов хранимых процедур
Существуют два способа обработки результатов хранимых процедур:
- хранимая процедура возвращает результирующее множество
- результат выполнения хранимой процедуры помещается в OUT параметры команды
Для первого способа используется обычная SQL-инструкция:
select * from stored_procedure_name(…)
Результат её выполнения обрабатывается при помощи объекта OleDbDataReader:
public void StoredProcedureResultSetTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
//select stored procedure in params OleDbCommand cmd_in_params = new OleDbCommand("select cust_no from CUSTOMER", con, trans); //select mail label through the stored procedure OleDbCommand cmd_stored_proc = new OleDbCommand("select * from mail_label(:cust_no)", con, trans); //add one IN parameter cmd_stored_proc.Parameters.Add("cust_no", OleDbType.Integer);
//execure reader using (OleDbDataReader rdr = cmd_in_params.ExecuteReader(CommandBehavior.CloseConnection)) { //for each customer No while (rdr.Read()) { cmd_stored_proc.Parameters["cust_no"].Value = rdr["cust_no"]; using (OleDbDataReader rdr_out = cmd_stored_proc.ExecuteReader()) { Console.WriteLine("Customer №" + rdr["cust_no"]); while (rdr_out.Read()) for (int i = 0; i < rdr_out.FieldCount; i++) Console.WriteLine(rdr_out.GetName(i) + "=" + rdr_out[i]);
Console.WriteLine(); } }
} }
Второй способ – вызов хранимой процедуры через инструкцию:
execute procedure stored_procedure_name
Результат выполнения помещается в OUT параметры команды, которые предварительно необходимо создать:
public void StoredProcedureOUTParamsTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
//select in params OleDbCommand cmd_in_params = new OleDbCommand("select cust_no from CUSTOMER", con, trans);
//STORED PROCEDURE OleDbCommand cmd_stored_proc = new OleDbCommand("execute procedure mail_label(:cust_no)", con, trans);
//IN parameter cmd_stored_proc.Parameters.Add("cust_no", OleDbType.BSTR); //OUT parameters cmd_stored_proc.Parameters.Add("line1", OleDbType.BSTR) .Direction = ParameterDirection.Output; cmd_stored_proc.Parameters.Add("line2", OleDbType.BSTR) .Direction = ParameterDirection.Output; cmd_stored_proc.Parameters.Add("line3", OleDbType.BSTR) .Direction = ParameterDirection.Output; cmd_stored_proc.Parameters.Add("line4", OleDbType.BSTR) .Direction = ParameterDirection.Output; cmd_stored_proc.Parameters.Add("line5", OleDbType.BSTR) .Direction = ParameterDirection.Output; cmd_stored_proc.Parameters.Add("line6", OleDbType.BSTR) .Direction = ParameterDirection.Output;
//execure reader using (OleDbDataReader rdr = cmd_in_params.ExecuteReader()) { //for each customer No while (rdr.Read()) { cmd_stored_proc.Parameters["cust_no"].Value = rdr["cust_no"]; cmd_stored_proc.ExecuteNonQuery();
Console.WriteLine("Customer №" + rdr["cust_no"]); Console.WriteLine(cmd_stored_proc.Parameters["line1"].Value); Console.WriteLine(cmd_stored_proc.Parameters["line2"].Value); Console.WriteLine(cmd_stored_proc.Parameters["line3"].Value); Console.WriteLine(cmd_stored_proc.Parameters["line4"].Value); Console.WriteLine(cmd_stored_proc.Parameters["line5"].Value); Console.WriteLine(cmd_stored_proc.Parameters["line6"].Value); Console.WriteLine(""); } }
trans.Commit(); con.Close(); }
Работа с массивамиADO .Net
может работать с любыми типами данных. Для тех типов Ole Db, у которых
нет прямого отображения на типы данных .Net, используется тип данных DBTYPE_VARIANT. Массивы относятся как раз к таким типам.
Следующий пример демонстрирует чтение и запись массива из 5 элементов:
public void ArrayReadWriteTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
OleDbCommand cmd = new OleDbCommand( "select job_code, job_grade, job_country, job_title, language_req from job", con, trans);
OleDbCommand cmd_upd = new OleDbCommand( "update job set language_req=:language_reg where \n" + "job_code=:job_code and job_grade=:job_grade and job_country=:job_country", con, trans);
cmd_upd.Parameters.Add("language_req", OleDbType.Variant); cmd_upd.Parameters.Add("job_code", OleDbType.BSTR); cmd_upd.Parameters.Add("job_grade", OleDbType.BSTR); cmd_upd.Parameters.Add("job_country", OleDbType.BSTR);
using (OleDbDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { Console.WriteLine("JOB TITLE:" + rdr["job_title"].ToString());
//чтение массива object lang_obj_arr = rdr["language_req"];
if (lang_obj_arr != DBNull.Value) { //преобразование к массиву //используем Array.CreateInstance для создания массива //из 5 элементов, с адресацией начиная с 1-го элемента, а не с 0 short arr_lower_bound = 1;
Array lang_str_arr = Array.CreateInstance(typeof(string), new int[] {5}, new int[] { arr_lower_bound });
//копирование элементов в массив ((Array)lang_obj_arr).CopyTo(lang_str_arr, arr_lower_bound);
for (int i = arr_lower_bound; i < lang_str_arr.Length + arr_lower_bound; i++) { //усечение символа \n на концах элементов массива string trimmed_value = lang_str_arr.GetValue(i).ToString().Replace("\n", ""); lang_str_arr.SetValue(trimmed_value, i);
//вывод значения if (lang_str_arr.GetValue(i).ToString() != "") Console.WriteLine(lang_str_arr.GetValue(i)); }
//запись новых значений элементов массива без символа \n cmd_upd.Parameters["language_req"].Value = lang_str_arr; cmd_upd.Parameters["job_code"].Value = rdr["job_code"]; cmd_upd.Parameters["job_grade"].Value = rdr["job_grade"]; cmd_upd.Parameters["job_country"].Value = rdr["job_country"]; //передача изменений в БД Assert.IsTrue(cmd_upd.ExecuteNonQuery() == 1); } else Console.WriteLine("No language specified");
Console.WriteLine(""); } }
//откат сделанных изменений trans.Rollback(); con.Close(); }
ПРИМЕЧАНИЕ. В примере использован базовый класс Array и метод CreateInstance для создания массива строк. В C# адресация массивов начинается с нулевого элемента, а в данном случае в базе данных записан массив, который проиндексирован, начиная с первого элемента. Array.CreateInstance()
позволяет указать нижнюю границу массива элементов. В случае массивов с
нулевой адресацией достаточно использования типизированных наследников
класса Array, например string[], int[] и т.д. |
Работа с BLOB полями
IBProvider поддерживает работу с двумя типами BLOB полей: содержащих
текст и бинарные данные. Не могу не заметить, что при использовании
этого провайдера работа с BLOB полями происходит так же, как и с
обычными типами данных:
public void BLOBReadWriteTest() { OleDbConnection con = ConnectionProvider.CreateConnection(); con.Open(); OleDbTransaction trans = con.BeginTransaction();
//BLOB Read command OleDbCommand cmd = new OleDbCommand( "select proj_id, proj_name,proj_desc from project", con, trans);
//BLOB write command OleDbCommand cmd_update = new OleDbCommand( "update project set proj_desc=:proj_desc where proj_id=:proj_id", con, trans); //create parameters with BSTR type cmd_update.Parameters.Add("proj_desc", OleDbType.BSTR); cmd_update.Parameters.Add("proj_id", OleDbType.BSTR);
using (OleDbDataReader rdr = cmd.ExecuteReader()) { while (rdr.Read()) { //чтение BLOB Console.WriteLine("PROJECT: " + rdr["proj_name"].ToString()); Console.WriteLine(rdr["proj_desc"].ToString());
//запись BLOB cmd_update.Parameters["proj_id"].Value = rdr["proj_id"];
//каждый раз меняем регистр данных в BLOB поле string new_project_description = rdr["proj_desc"].ToString(); if (new_project_description.ToUpper() != new_project_description) new_project_description = new_project_description.ToUpper(); else new_project_description = new_project_description.ToLower();
cmd_update.Parameters["proj_desc"].Value = new_project_description; Assert.AreEqual(1, cmd_update.ExecuteNonQuery()); } }
trans.Commit(); con.Close(); }
Здесь тип параметров команды обновления установлен в OleDbType.BSTR.
В этом случае провайдер корректно распознает тип параметров и
произведет их преобразование к типам базы данных.
СОВЕТ. В примере OleDbDataReader использован совместно с конструкцией using. Он поддерживает интерфейс IDispose и после завершения работы сам позаботится о своем закрытии, а если в метод OleDbCommand.ExecuteReader() передать значение CommandBehavior.CloseConnection, то так же будет закрыто подключение к базе данных. |
MARS - Multiple Active Result Sets
В ADO .Net 2.0 появилась «новая» технология, которая получила название MARS. В Net Framework 1.1. в одном контексте транзакции было невозможно держать открытый OleDbDataReader и параллельно выполнять дополнительные запросы к базе данных или открывать ещё один OleDbDataReader. При попытке выполнить этот трюк мы получали исключение вида:
«There is already an open DataReader associated with this Connection which must be closed first.»
Предыдущий пример работы с BLOB полями как раз и показывает
применение технологии MARS. В нем демонстрируется последовательное
чтение данных и их одновременное их обновление.
Если обратиться к истории, то мы обнаружим, что технология эта
совсем не новая, да и технологией назвать это сложно. Если сравнить
вторую версию ADO .Net с первой, то, конечно, разработчики добились
определенных успехов. Но возможность использовать несколько RecordSet в одной транзакции была реализована ещё в классическом ADO. Скажу больше, там можно было использовать несколько RecordSet, связанных
с одной командой. Это достигалось за счет клонирования команды внутри
себя, если обнаруживалось, что уже есть связанное с ней множество. В
ADO .NET команда тоже умеет клонировать саму себя. Для этого есть метод
Clone(), который необходимо вызывать явно, если вы хотите связать несколько OleDbDataReader с одной командой.
Таким образом, применение MARS возможно не только для MS SQL Server, как пишут во многих источниках информации, но и для других баз данных.
|