Особенности работы с Oracle в Visual Studio 2003 (ASP.NET 1)Вместо предисловияДанный материал написан для корпоративного использования. В силу этого, основное внимание сосредоточено на вопросах, которые, так или иначе, интересны автору для повседневной работы. Но, так как материал, помещенный в Inet, имеет тенденцию развиваться далее, за счет критических замечаний и дополнений, которые, хотя и редко (по опыту опубликованных материалов по Builder), но иногда все же присылаются автору, то я решил поделиться этим материалом с Вами. Жду замечаний и пожеланий, материалов с дополнениями и т.п. (адрес wladm@narod.ru).
Для рассмотрения данного материала создадим новый проект (Рис.1) с именем AppBD, с панелью инструментов на базе контрола ToolBar с тремя кнопками. (Подробно о создании проектов решения см. в разделе "Простейшие Windows Application решения").
Рис.1 Форма проекта решения Установим свойство Tag у кнопок в значения 1, для кнопки 1 (левой), 2 для кнопки 2. Создадим обработчик нажатия кнопок для ToolBar и запишем следующий код, который далее будем использовать для всех примеров этого параграфа. private void toolBar1_ButtonClick(object sender, System.Windows.Forms.ToolBarButtonClickEventArgs e) { int i=Convert.ToInt32(e.Button.Tag); switch(i) { case 1: break; case 2: break; default: Application.Exit(); break; } } В case 1 и 2 будем размещать код, выполняющий какое либо действие с БД (ввод или вывод информации), в default действия по закрытию приложения (выход из приложения). Особенности использования провайдера данных для OracleДля работы с Oracle в Microsoft Visual Studio.NET используется специально созданный провайдер. Соответственно объекты Connection, DataAdapter и Command для Oracle - это OracleConnection, OracleDataAdapter, OracleCommand - имеют полностью аналогичные возможности и интерфейс. Однако есть некоторые несущественные нюансы, рассмотренные ниже: Пространство имен провайдера данных .NET Framework для OracleOracleConnection, OracleCommand, OracleDataAdapter являются объектами класса OracleClient, что скрыто при использовании визуальных компонент, но приходится учитывать при программном формировании наследников данных классов. Поэтому, необходимо, включить пространство имен: using System.Data.OracleClient; При включении директивы, следующий код корректен, и выведет данные в DataGrid: case 1: OracleConnection myConnection = new OracleConnection (); myConnection.ConnectionString = "user id=myUser;data source=mySource; password=myPassword"); try { myConnection.Open(); OracleDataAdapter MyDataAdapter=new OracleDataAdapter(); string sMyCommand="SELECT * FROM DEPT"; MyDataAdapter.SelectCommand=new OracleCommand(sMyCommand); MyDataAdapter.SelectCommand.Connection=myConnection; MyDataAdapter.SelectCommand.CommandText=sMyCommand; DataSet MyDataSet = new DataSet(); MyDataAdapter.Fill(MyDataSet,"Table"); dataGrid1.DataSource=MyDataSet; dataGrid1.DataMember = "Table"; } catch (Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; При использовании визуальных компонент, процесс создания решения не требует включения пространства имен OracleClient и может быть выполнен следующим образом: На заготовку проекта решения (Рис.1), помещаем OracleDataAdapter (закладка Data окно Toolbox). Прежде чем компонент займет свое место внизу окна дизайнера формы, будет запущен Data Adapter Configuration Wizard, на панели которого, после прочтения информации о назначении мастера и нажатия кнопки Next, будет предложено создать новое или выбрать уже существующее подключение к базе данных. Нажимаем кнопку New Connection и переходим к окну Data Link Properties. На вкладке Provider выбираем Microsoft Ole DB Provider for Oracle, на вкладке Connection задаем требуемые параметры подключения (ServerName, UserName и Password). Далее проверяем TestConnection и нажимаем кнопку OK. В окошечке Data Adapter Configuration Wizard появляется имя нашего соединения. Нажимаем кнопку Next и в следующем окошечке выбираем Use SQLStatements. Обратим внимание, что этот выбор, в отличие от SQL сервера единственный (работа с сохраненными процедурами будет рассмотрена ниже). Нажимаем кнопочку Next и попадаем в окно, где можно задать SQL оператор для выборки данных. Воспользуемся QueryBuilder и в окне AddTable выберем нашу таблицу (например, Dept), выбираем интересующие нас поля и после нажатия кнопочки OK увидим сформированный нами запрос к таблице (его мы могли записать и без использования QueryBuilder). SELECT DEPTNO, DNAME, LOC FROM DEPT Обратим внимание на кнопочку Advanced Option. Адаптер данных создает четыре предложения SQL. Помимо Select создается Insert, Update, Delete. Если нам требуется только Select, то можно убрать галочку из первого пункта (в чекбокске). При наличии галочки команды Insert и Update могут быть модифицированы. Отличие от сервера SQL здесь в том, что отсутствует возможность использовать Refresh the DataSet. Тоесть, в Oracle нет возможности добавить select после insert и update для проверки корректность значений идентификаторов, значений по умолчанию, и расчетных значений после внесения изменений. Назначение опции Use Optimistic concurrency полностью аналогично ее назначению в SQL .Net провайдерре (оптимистический параллелизм, который позволяет командам Update и Delete обнаружить наличие в база данных изменений после того как данные были загружены в набор данных). Однако при включении этой опции команды Update и Delete для Oracle не формируются, и, поэтому, целесообразно убрать галочку в чекбоксе против данной опции. Заканчиваем формирование, нажав кнопочку Next, Data Adapter Configuration Wizard генерирует заданные параметры и, после нажатия кнопки Finish, OracleDataAdapter и OracleConnection занимают свои места для невизуальных контролов в нижней части панели дизайнера формы. Генерация DataSet полностью аналогична его генерации для провайдера SQL сервера - выбираем в контексном меню oracleDataAdapter1 пункт Generate DataSet (тоже из меню Data). Будет запущен мастер Generate DataSet в котором оставим все установки по умолчанию (флажок Add this DataSet to the designer- добавить этот Dataset в конструктор должен быть установлен) и нажимаем кнопку ОК. DataSet отображается в панели для невизуальных контролов. Помещаем на форму DataGrid, в выпадающем списке его свойства DataSource выбираем dataSet11, а значение свойства DataMember в значение Dept. Для заполнения таблицы в Case 1 запишем следующий код (набор данных заполняется методом Fill адаптера данных): case 1: dataSet11.Clear(); oracleDataAdapter1.Fill(dataSet11); break; Данные отображаются в DataGrid. Особенности задания параметров Select предложенияПараметры при формировании предложения Select могут быть заданы как в QueryBuilder при формировании DataAdapter, так и в окне Properties адаптера, аналогично SqlDataAdapter. При задании параметров вместо значка "@" используется значок ":". SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE (DEPTNO <= :PARAM1) ORDER BY DEPTNO Задание параметра в коде программы будет выглядеть следующим образом: oracleDataAdapter1.SelectCommand.Parameters[":Param1"].Value = 1000; Параметры в Updates коммандах также выделяются ":" перед именем параметра. Возможность включения ключевой информации в таблицы DataSetКогда OracleDataAdapter заполняет DataSet, создаются необходимые таблицы и столбцы для возвращенных данных, если их еще не существует. Однако данные о первичном ключе не будут включены в неявно созданную схему, если свойство MissingSchemaAction не имеет значение AddWithKey. MyDataAdapter.MissingSchemaAction=MissingSchemaAction.AddWithKey; С помощью OracleDataAdapter можно также создать схему DataSet, включая данные о первичном ключе, перед заполнением ее данными с помощью метода FillSchema. MyDataAdapter.FillSchema(MyDataSet, SchemaType.Source); MyDataAdapter.Fill(MyDataSet,"Table"); dataGrid1.DataSource=MyDataSet; dataGrid1.DataMember = "Table"; Использование схемы для работы с таблицами будет рассмотрено ниже, в параграфе "Типы данных Oracle и особенности их применения ". Использование пакетного заполнения DataSetПровайдер данных .NET Framework для Oracle не поддерживает пакетные предложения SQL. Однако есть возможность использовать для заполнения DataSet несколько выходных параметров типа REF CURSOR, возвращаемых хранимой процедурой. Отметим, что когда OracleDataAdapter заполняется из параметров REF CURSOR хранимой процедуры, то невозможно использовать метод Update, поскольку будут отсутствовать сведения, необходимые для определения имен таблиц и имен столбцов при выполнении SQL предложения. В следующем примере (уточненный из MSDN) создается хранимая процедура и производится выборка с ее помощью данных из таблицы Emp и Dept. Создадим, например, в SQL+ хранимую процедуру: create or replace package sp_pkg as type refCursorxx is ref cursor; procedure getdata(a1 out refCursorxx, a2 out refCursorxx); end; create or replace package body sp_pkg as procedure getdata( out refCursorxx, a2 out refCursorxx) is begin open a1 for select * from emp; open a2 for select * from dept; end getdata; end; Выборку данных выполним с использованием следующего кода: OracleConnection myConnection =new OracleConnection(); myConnection.ConnectionString = "user id=myUser;data source=mySource; password=myPassword"; try { myConnection.Open(); OracleCommand MyOrCommand=myConnection.CreateCommand(); //SQL - предложение - вызов хранимой процедуры MyOrCommand.CommandText="sp_pkg.getdata"; MyOrCommand.CommandType = CommandType.StoredProcedure; //Задаем параметры для хранимой процедуры MyOrCommand.Parameters.Add(new OracleParameter("a1", OracleType.Cursor)).Direction = ParameterDirection.Output; MyOrCommand.Parameters.Add(new OracleParameter("a2", OracleType.Cursor)).Direction = ParameterDirection.Output; //Создаем DataSet DataSet MyDataSet = new DataSet(); //Создаем DataAdapter OracleDataAdapter MyDataAdapter= new OracleDataAdapter(MyOrCommand); //Заполняем DataSet данными, возвращенными процедурой //Причем два курсора - два потока - //один заполнит Table, второй Table1 MyDataAdapter.Fill(MyDataSet); //Создаем отношение между таблицами и выводим MyDataSet.Relations.Add("Relation", MyDataSet.Tables["Table"].Columns["deptno"], MyDataSet.Tables["Table1"].Columns["deptno"]); //Отображаем связные таблицы dataGrid1.DataSource=MyDataSet; dataGrid1.DataMember = "Table"; dataGrid2.DataSource=MyDataSet; dataGrid2.DataMember = "Table.Relation"; } catch (Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } Использование запроса к хранимой процедуре с использованием визуальных компонент имеет несколько технических особенностей. В заготовку проекта решения поместим и настроим контрол OracleConnection. Далее поместим OracleCommand. В его свойствах выберем: Command Type - StoredProcedure, Connection - oracleConnection1, Command Text - sp_pkg.getdata (вызов нашей хранимой процедуры). При выходе из Command Text будет выведен запрос: "Do you want to regenerate the Parameters the parameters collection this command" - отвечаем No и задаем параметры, вызвав Oracle Parameters Collection Editor в свойстве Parameters. Задаем параметры как показано на Рис.2.
Рис.2 Задание параметров для хранимой процедуры Поместим на форму OracleDataAdapter и на запрос на запуск Data Adapter Configuration Wizard, нажимаем Cancel. Контрол займет свое место в панели для не визуальных компонент. Задаем его свойство SelectCommand - Existing oracleCommand1. Генерируем DataSet, соглашаемся с предложенными параметрами. В case 2 пишем код, который выполняет аналогичные действия по загрузке двух таблиц, и выполняет их объединение по принципу master-detail: case 2: try { oracleConnection1.Open(); oracleDataAdapter1.Fill(dataSet11); dataSet11.Relations.Add("Relation", dataSet11.Tables["Table"].Columns["deptno"], dataSet11.Tables["Table1"].Columns["deptno"]); dataGrid1.DataSource=dataSet11; dataGrid1.DataMember = "Table"; dataGrid2.DataSource=dataSet11; dataGrid2.DataMember = "Table.Relation"; } catch (Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { oracleConnection1.Close(); } break; Особенности использования OracleCommand1. В отличие от объекта Command других провайдеров данных .NET Framework (SQL Server, OLE DB и ODBC) объект OracleCommand не поддерживает свойство CommandTimeout. Задание времени ожидания для команды бесполезно, а возвращаемое значение всегда равно нулю. 2. OracleCommand, помимо методов ExecuteReader, ExecuteNonQuery, ExecuteScalar имеет два спицифических метода:
Для демонстрации возможностей методов, поместим на заготовку формы (Рис.1.) OracleConnection, настроив его на соединение с БД и OracleCommand, установив его свойство Connection в oracleConnection1. Выполним следующий код и получим в свойстве Text формы значение суммы значений поля Sal таблицы Emp. case 1: oracleConnection1.Open(); oracleCommand1.CommandText="Select Sum(Sal) from emp"; Тext=Convert.ToString(oracleCommand1.ExecuteOracleScalar()); oracleConnection1.Close(); break; Из других методов OracleCommand отметим:
Типы данных Oracle и особенности их использованияТипы данных .NET Framework для работы с Oracle
Пространство имен System.Data.OracleClient соответственно имеет и классы и структуры для поддержки специфических типов данных, это:
Работа с Any типом данных OracleТип данных Oracle Any позволяет в одном столбце таблицы Oracle хранить данные различных типов. Однако, при использовании провайдера данных .NET Framework для Oracle, мы практически не сможем работать с контролами DataAdapter и DataCommand, а использование соответствующих объектов имеет определенные сложности. Например, создадим таблицу: SQL>CREATE TABLE MyTable (col1 NUMBER,col2 SYS.ANYDATA) Таблица создана. SQL> desc myTable; Имя Пусто? Тип ------------------- -------- ------------ COL1 NUMBER COL2 SYS.ANYDATA При попытке создать SQL предложение в QueryBuilder при конфигурировании OracleDataAdapter на выбор данных из MyTable - получим сообщение: Unsupported Oracle data type USERDEFINED encountered. Аналогичный эффект будет и при попытке загрузить схему данных, при создании DataReader в случае использования OracleCommand для работы с данными и т.д. И, если в SQL предложениях существует возможность записать в столбец с типом данных SYS.ANYDATA большинство типов данных Oracle (опять же не все и не так просто - о чем ниже), то, к сожалению, простые способы вывода отсутствуют. Предполагается, что программа будет извлекать данные с использованием хранимых процедур и функций Oracle, определять, что они из себя представляют, и подвергать их преобразованию в зависимости от типа. Следующий пример показывает, как возможно загрузить данные в базу данных. Если для простых типов данных (DATE, NUMBER, CHAR, VARCHAR, VARCHAR2) это не сложно, то для остальных типов возникает ряд проблем. Мы уже сказали о невозможности использовать при загрузке данных схемы данных (как, например, это будет показано ниже при рассмотрении работы с Lob и RAW типами). По той же причине, нет возможности и выбрать данные для Update. Единственный способ записи данных - прямое использование предложения Insert в OracleCommand. Но и здесь возникают проблемы с тем, что для вставки данных длиной более 4000б мы получим исключение: ORA 01704 String Literal is Long Кроме того, запись Lob и Rav типов потребует дополнительных ухищрений, связанных с тем, что данные в предложении Insert трактуются Oracle как строка и при передаче двоичных данных некоторые совокупности байт могут трактоваться Oracle как спецсимволы, например конца строки. В примере показано как обойти и данную ситуацию: using System.IO; using System.Data.OracleClient; ....... case 1: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); //Elfkztv nf,kbwe - если нет обходим удаление try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER,col2 SYS.ANYDATA)"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(1, SYS.ANYDATA.CONVERTVARCHAR2('My String'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(2, SYS.ANYDATA.CONVERTNUMBER(1000))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(3, SYS.ANYDATA.CONVERTDATE('28.03.2005'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(4, SYS.ANYDATA.CONVERTCHAR('My String'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(5, SYS.ANYDATA.CONVERTVARCHAR('My String'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(6, SYS.ANYDATA.CONVERTVARCHAR2('My String'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(7, SYS.ANYDATA.CONVERTNUMBER(10.5))"; myCommand.ExecuteNonQuery(); //Далее мы могли бы вставить данные типа Raw (аналогично LOB) так //как в следующих двух строках кода, но при этом - это будут //не двоичные даннае, да и пробел здесь не допустим myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(8, SYS.ANYDATA.CONVERTRAW('My_String'))"; myCommand.ExecuteNonQuery(); //Поэтому поступаем так, как показано ниже, причем размер //файла должен бать не более 2Мб. int viLength=0; byte[] buffer=null; //Поток для файла картинки размером 2Мб using(FileStream filestream= new FileStream(@"C:\a.bmp",System.IO.FileMode.Open)) { viLength=(int)filestream.Length; buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); } //Кодируем содержимое buffer в строку, в которой заведома //не будет символа конца строки StringBuilder mystringbuilder=new StringBuilder(); foreach(byte b in buffer) { mystringbuilder.Append(b.ToString("X2")); } myCommand.CommandText = "INSERT INTO MyTable (col1, col2) VALUES(7, SYS.ANYDATA.CONVERTRAW('"+mystringbuilder.ToString()+"'))"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Для вывода данных создадим Oracle функцию: function getData(typecoll in sys.anyData ) return varchar2 as out_num number; out_long long; out_date date; out_varchar2 varchar2(4000); begin case typecoll.gettypeName when 'SYS.NUMBER' then if(typecoll.getNumber(out_num) = dbms_types.success) then out_varchar2 := out_num; end if; when'SYS.DATE' then if(typecoll.getDate(out_date) = dbms_types.success ) then out_varchar2 := out_date; end if; when 'SYS.VARCHAR' then if(typecoll.getVarchar(out_varchar2) = dbms_types.success ) then null; end if; when 'SYS.VARCHAR2' then if(typecoll.getVarchar2(out_varchar2) = dbms_types.success ) then null; end if; when 'SYS.CHAR' then if(typecoll.getChar(out_varchar2) = dbms_types.success) then null; end if; when 'SYS.RAW' then if(typecoll.getRaw(out_varchar2) = dbms_types.success) then null; end if; when 'SYS.LONG' then if(typecoll.getVarchar2(out_long) = dbms_types.success ) then out_varchar2 := out_long; end if; else out_varchar2 := 'unknown data type'; end case; return out_varchar2; end; Следующий код позволит отобразить данные, так как все они будут приведены к типу VARCHAR2. Однако для отображения картинки, записанной как тип данных Raw - потребуется несколько промежуточных преобразований (PictureBox помещен на форму): case 2: string myconnectionstring="user id=myUser;data source=mySource; password=myPassword"; OracleConnection myConnection = new OracleConnection(myconnectionstring); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText="select getData(col2 ) getdata from mytable"; //Выбираем данные с использованием созданной нами функции GETDATA myCommand.CommandText="SELECT col1, MyTableOut.Col2.GetTypeName(), //Тип данных GETDATA(Col2) dataValue //Значение FROM MyTable MyTableOut"; try { OracleDataReader myReader = myCommand.ExecuteReader(); using(myReader) { while(myReader.Read()) { //Выводим тип данных и значения MessageBox.Show("Запись: "+myReader.GetInt32(0).ToString() + " Тип поля: "+myReader.GetString(1)+" Значение: "+myReader.GetString(2)); if(myReader.GetString(1) == "SYS.RAW") { //Извлекаем картинку string s=myReader.GetString(2); byte[] buffer=new byte[s.Length]; uint uiHex = 0; //Данные строки в byte[], причем в виде 16ричных знаков for(int i=0; i < s.Length; i+=2) { uiHex = Convert.ToUInt32(s.Substring(i,2),16); buffer[i/2]=(byte)uiHex; } using(MemoryStream memorystream = new MemoryStream()) { memorystream.Write(buffer,0,buffer.Length); //Отображаем картинку pictureBox1.Image=Image.FromStream(memorystream); } } } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Работа с LOB-типами данныхLOB-типы используются для хранения больших объектов (до 4 Гбайт- Large Object). В Oracle реализован более полный набор LOB-типов, чем предусмотренный стандартом SQL-99. Типы LOB отличаются от типа LONG тем, что данные хранятся не в табличном пространстве, а в файлах ОС (BFILE) или сервера БД (BLOB, CLOB, NCLOB), а в таблице хранится указатели на данные (локаторы), которые и возвращается при выполнении предложений Select. Oracle поддерживает следующие четыре типа для больших объектов:
Для работы с Lob типами в Net используется OracleLob класс. Рассмотрим работу с BLOD, CLOB, NCLOB типами данных (работу с BFile рассмотрим в следующем параграфе). Работа с Lob типами с использованием OracleCommandСледующий пример демонстрирует методику работы с Lob типами с использованием OracleCommand. В примере не использованы контролы, что не является принципиальным. В case 1 вначале создается и заполняется таблица, в которой представлены все указанные типы данных. Oracle Lob позволяют создать только пустой объект, который, хотя и имеет метод Write, но возможность переноса данных в объект допустима только тогда, когда локатор этого объекта соответствует реальному в таблице базы данных. В силу этого, первоначально, мы вставляем в таблицу базы данных строку с произвольными строковыми значениями полей данных и, только после этого, выбрав строку (уже содержащую реальные локаторы) для UPDATE, соотносим столбцы с данными. Для выполнения примера текстовые данные занесем в файлы a.txt и b.txt, а картинки поместим в файлы a.bmp, a.jpg и a.gif. case 1: byte[] buffer=null; OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); //Если таблица существует, то удаляем ее, так как будем создавать //ее заново. Если не существует, то обходим удаление по Exception try { myCommand.CommandText = "drop Table myTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } //Создаем новую таблицу try { myCommand.CommandText = "create table myTable(col1 int, col2 BLOB, col3 CLOB, col4 NCLOB)"; myCommand.ExecuteNonQuery(); } catch(Exception ex2) { MessageBox.Show("Исключение:\n"+ex2.Message); return; } OracleLob oraclelob = OracleLob.Null; //Записываем в таблицу любую запись, которую затем будем //изменять - это позволит создать локаторы try { myCommand.CommandText = "INSERT INTO myTable(col1,col2, Col3,Col4) values (1,'1','2',N'3')"; myCommand.ExecuteNonQuery(); } catch(Exception ex2) { MessageBox.Show("Исключение:\n"+ex2.Message); return; } //Выбираем созданную запись для изменения myCommand.CommandText = "select * from MyTable FOR UPDATE"; //Открываем транзакцию - работа с большими объемами данных OracleTransaction myTransaction= myConnection.BeginTransaction(IsolationLevel.ReadCommitted); myCommand.Transaction = myTransaction; //Обновляем значения полей, но не локаторы //Значения: для внутреннего двоичного объекта, OracleLob BLOB=OracleLob.Null; //для внутренних символьных объектов. OracleLob CLOB=OracleLob.Null; OracleLob NCLOB=OracleLob.Null; //Для доступа к значениям полей создаем Reader OracleDataReader myReader = myCommand.ExecuteReader(); //Переносим ресунок в буфер, используя FileStream int viLength=0; using(FileStream filestream= new FileStream(@"C:\a.jpg",System.IO.FileMode.Open)) { viLength=(int)filestream.Length; buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); } try { using(myReader) { myReader.Read(); BLOB=myReader.GetOracleLob(1); BLOB.BeginBatch(OracleLobOpenMode.ReadWrite); BLOB.Write(buffer,0,viLength); BLOB.EndBatch(); //Текст берем из фала using(FileStream filestream = new FileStream(@"C:\a.txt",FileMode.Open)) { using(TextReader streamreader = new StreamReader(filestream)) { CLOB=myReader.GetOracleLob(2); //Производим позиционирование в потоке, ассоциированном с файлом filestream.Seek(0,SeekOrigin.Begin); string s=streamreader.ReadToEnd(); CLOB.BeginBatch(OracleLobOpenMode.ReadWrite); CLOB.Write(Encoding.Unicode.GetBytes(s),0,s.Length*2); CLOB.EndBatch(); } } using(FileStream filestream = new FileStream(@"C:\b.txt",FileMode.Open)) { using(TextReader streamreader = new StreamReader(filestream,System.Text.Encoding.Default)) { NCLOB=myReader.GetOracleLob(3); //Производим позиционирование в потоке, ассоциированном с файлом filestream.Seek(0,SeekOrigin.Begin); string s=streamreader.ReadToEnd(); NCLOB.BeginBatch(OracleLobOpenMode.ReadWrite); NCLOB.Write(Encoding.Default.GetBytes(s),0,s.Length); NCLOB.EndBatch(); } } } } catch(Exception ex2) { MessageBox.Show("Исключение:\n"+ex2.Message); } finally { myTransaction.Commit(); oracleConnection1.Close(); } break; Код чтения поместим в case2. Для отображения рисунка поместим PictureBox на форму. case 2: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); OracleLob oraclelob = OracleLob.Null; string string1=null,string2=null; int count=0; try { myCommand.CommandText = "select * from myTable"; System.Data.OracleClient.OracleDataReader myReader = myCommand.ExecuteReader(); OracleLob oraclelob = OracleLob.Null; string string1=null,string2=null; int count=0; using(myReader) { while(myReader.Read()) { if(!myReader.IsDBNull(2)) { oraclelob = myReader.GetOracleLob(2); StreamReader streamreader = new StreamReader(oraclelob, Encoding.Unicode); using(streamreader) { while((count=streamreader.Read(cbuffer, 0,(int)oraclelob.Length)) > 0) { string1=new string(cbuffer, 0, count); } } } if(!myReader.IsDBNull(3)) { oraclelob = myReader.GetOracleLob(3); StreamReader streamreader = new StreamReader(oraclelob, Encoding.Default); using(streamreader) { while((count=streamreader.Read(cbuffer,0,cbuffer.Length)) > 0) { string2=new string(cbuffer, 0, count); } } } if(!myReader.IsDBNull(1)) { oraclelob = myReader.GetOracleLob(1); MemoryStream memorystream = new MemoryStream((byte[])oraclelob.Value); using(memorystream) { pictureBox1.Image=Image.FromStream(memorystream); MessageBox.Show("Отображена картинка номер "+ Convert.ToString(myReader["col1"])+ "\n Текст CLOB: "+ string1+"\n Текст CLOB: "+string2); } } } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Результат выполнения кода показан на Рис 3.
Рис.3 Работа с Lob данными в Oracle Выполнение этого примера с использованием контролов OracleCommand и OracleConnection полностью аналогично, за исключением того, что при настройке OracleCommand должно быть установлено только свойство Connection в OracleConnection1. Отметим, что провайдер данных NET Framework для Oracle обрабатывает все данные CLOB и NCLOB как Юникод. Поэтому целесообразно напрямую указывать Encoding при записи и чтении данных. Иначе, при осуществлении доступа к типам данных CLOB и NCLOB когда выполняются операции с несколькими байтами, то каждому знаку соответствует 2 байта. Например, если текстовая строка, содержащая 3 знака, сохранена как NCLOB на сервере Oracle, где в наборе знаков под каждый знак отводится 4 байта и выполняется операция Write, следует указать длину строки 6 байтов, несмотря на то, что она сохранена на сервере в 12 байтах. Работа с Lob типами с использованием OracleDataadabterВ следующем примере демонстрируется методика работы с Lob типами, основанная на возможности загрузки объекта DataTable метаданными о столбцах (схемы данных). В case 1, как и в предыдущем примере, создается таблица в базе данных c типами данных столбцов BLOB, CLOB и NCLOB. Далее производится загрузка схемы данных в объект базы данных DataTable через DataAdapter (метод FillSchema). К таблице в памяти добавляется новая строка, которая теперь имеет типы данных, соответствующие типам реальных данных и, становится возможно присваивать данные. После заполнения данные могут быть переданы в таблицу БД. case 1: TextReader streamreader=null; byte[] buffer=null; OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); try { myConnection.Open(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } finally {} try { myCommand.CommandText = "create table MyTable(col1 number,col2 BLOB,col3 CLOB,col4 NCLOB)"; myCommand.ExecuteNonQuery(); } catch(Exception ex2) { MessageBox.Show("Исключение:\n"+ex2.Message); } using(FileStream filestream= new FileStream(@"C:\a.bmp",System.IO.FileMode.Open)) { buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); } string connectionstring="user id=scott;data source=mybase;password=mypasword"; OracleDataAdapter myDataAdapter = n ew OracleDataAdapter("SELECT col1,Col2,col3,col4 FROM MyTable", connectionstring); DataTable datatable = new DataTable(); myDataAdapter.FillSchema(datatable, SchemaType.Source); OracleCommandBuilder commandbuilder = new OracleCommandBuilder(myDataAdapter); DataRow datarow = datatable.NewRow(); datarow[0]=1; datarow[1]=buffer; using(FileStream filestream = new FileStream(@"C:\a.txt",FileMode.Open)) { using(streamreader = new StreamReader(filestream,System.Text.Encoding.Default)) { filestream.Seek(0,SeekOrigin.Begin); string s=streamreader.ReadToEnd(); datarow[2]=s; } } using(FileStream filestream = new FileStream(@"C:\b.txt",FileMode.Open)) { using(streamreader = new StreamReader(filestream,System.Text.Encoding.Default)) { filestream.Seek(0,SeekOrigin.Begin); string s=streamreader.ReadToEnd(); datarow[3]=s; } } datatable.Rows.Add(datarow); using(FileStream filestream= new FileStream(@"C:\a.jpg",System.IO.FileMode.Open)) { buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); } datarow = datatable.NewRow(); datarow[0]=2; datarow[1]=buffer; //Короткие строки можно записать и так datarow[2]="Doggie in .jpg Format"; datarow[3]="Песик в формате .jpg"; datatable.Rows.Add(datarow); myDataAdapter.Update(datatable); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Код вывода данных в case 2 у нас сохранился с предыдущего примера. Результат выполнения показан на Рис.3. При использовании контролов OracleConnection и OracleDataAdapter код ничем не отличается от приведенного. Особенностью является то, что при настройке OracleDataAdapter таблица MyTable должна существовать в базе данных. Иначе, при конфигурировании DataAdapter, мы не сможем создать предложения Updates, даже при введении вручную предложение SQL в Query Bilder. Кроме того, необходимо снять галочку Use Optimistic concurrency на страничке Advanced Sql Generation Options после создания SQL предложения в Query Bilder. Работа с Lob с использованием временных Lob объектов и хранимых процедурСуществует также возможность заполнения полей Lob в таблицах БД через использование хранимых процедур. В этом случае специально создается хранимая процедура с параметрами Lob, а затем открывается временный LOB в Oracle путем вызова хранимой процедуры системы DBMS_LOB.CREATETEMPORARY и выполняется заполнение временного Lob и его привязка к выходным параметрам процедуры. На стороне клиента временный LOB ведет себя как табличный LOB. Пример создания временных Lob можно найти в MSDN и на домашних страницах центра справки и поддержки Microsoft. И, в тоже время, этот путь ничем не лучше описанных выше, поэтому, как пример для тех, кто хочет убедиться в этом, привожу код, найденный мной на сервере ASPNNetMedia , принадлежащий Максиму (фамилия не указана). создание хранимой процедуры CREATE OR REPLACE PROCEDURE PR_test(LOGIN IN VARCHAR2, PASSWORD IN BLOB) IS BEGIN insert into TEST (TEST) values(LOGIN);--просто вставим запись в таблицу END PR_test; //С# текст /* * Преобразование текста из TextBox в байтовый массив */ Encoding targetEncoding; byte[] ByteArray; targetEncoding = Encoding.GetEncoding(866); string str=TextBox1.Text; ByteArray = targetEncoding.GetBytes(str); //Открываем соединение с базой OracleConnection oracleConnection1=new OracleConnection(); oracleConnection1.ConnectionString= "Password=maxim;User ID=maxim; Data Source=test"; oracleConnection1.Open(); OracleTransaction tx ; tx = oracleConnection1.BeginTransaction(); OracleCommand cmd=new OracleCommand(); cmd =oracleConnection1.CreateCommand(); cmd.Transaction=tx; //Создаём временный блоб объект cmd.CommandText = "declare xx blob; begin dbms_lob.createtemporary(xx, false, 0); :tempblob := xx; end;"; cmd.Parameters.Add(new OracleParameter("tempblob", OracleType.Blob)).Direction = ParameterDirection.Output; cmd.ExecuteNonQuery(); //Заполняем блоб из байтового массива OracleLob tempLob; tempLob = (OracleLob)cmd.Parameters[0].Value; tempLob.BeginBatch(OracleLobOpenMode.ReadWrite); tempLob.Write(ByteArray, 0, ByteArray.Length); tempLob.EndBatch(); //Вызываем хранимую процедуру cmd.Parameters.Clear(); cmd.CommandText="PR_test"; cmd.CommandType=CommandType.StoredProcedure; cmd.Parameters.Add("LOGIN",OracleType.VarChar); cmd.Parameters["LOGIN"].Value="h"; cmd.Parameters.Add("PASSWORD",OracleType.Blob); cmd.Parameters["PASSWORD"].Value=tempLob; cmd.ExecuteNonQuery(); tx.Commit(); Класс OracleBFile и работа с типом данных BFILE OracleКласс OracleBFile предназначен для работы с типом данных BFILE Oracle. Oracle BFILE - это тип данных Oracle LOB, содержащий в таблице базы данных не сами данные, а ссылку на файл данных, который располагается в файлах ОС. Файлы могут иметь размер до 4 гигабайт. Тип данных BFILE предоставляет доступ к данным только для чтения. Тип данных BFILE, в отличие от типа данных LOB:
Расположение каждого непустого объекта OracleBFile определяет:
В следующем примере показано как можно отобразить рисунки, которые хранятся в директории "tmp" операционной системы. Информация о рисунках хранится в базе данных в виде объектов BFILE (рисунки в разных форматах с именами файлов a.bmp, a.jpg, a.gif заранее записаны в БД, а пользователю предоставлена привилегия GRANT CREATE ANY DIRECTORY TO SCOTT;): using System.Data.OracleClient; ......... case 1: OracleConnection myConnection = new OracleConnection("user id=myUserId;data source=mybase;password=mypassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText = "create or replace directory MYDIR as '/tmp'"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "GRANT READ ON DIRECTORY MYDIR TO PUBLIC"; myCommand.ExecuteNonQuery(); try { myCommand.CommandText = "drop table MyTable"; myCommand.ExecuteNonQuery(); }catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } myCommand.CommandText = "create table MyTable(col1 number, col2 BFILE)"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "insert into MyTable values ('1',BFILENAME('MYDIR','a.bmp'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "insert into MyTable values ('2',BFILENAME('MYDIR','a.jpg'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "insert into MyTable values ('3',BFILENAME('MYDIR','a.gif'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "select * from MyTable"; byte[] buffer =null; OracleDataReader myReader = myCommand.ExecuteReader(); using(myReader) { while(myReader.Read()) { OracleBFile myBFile = myReader.GetOracleBFile(1); buffer=new byte[myBFile.Length]; using(myBFile) { myBFile.Seek(0, SeekOrigin.Begin); myBFile.Read(buffer, 0, (int)myBFile.Length); MemoryStream memorystream = new MemoryStream(buffer); pictureBox1.Image=Image.FromStream(memorystream); MessageBox.Show("Отображена картинка: "+myBFile.FileName); } } } myConnection.Close(); break; Тот же пример с использованием контролов OracleConnection и OracleCommand (их настройка описана выше) case 1: try { oracleConnection1.Open(); oracleCommand1.CommandText = "create or replace directory MYDIR as '/tmp'"; oracleCommand1.ExecuteNonQuery(); oracleCommand1.CommandText = "GRANT READ ON DIRECTORY MYDIR TO PUBLIC"; oracleCommand1.ExecuteNonQuery(); try { oracleCommand1.CommandText = "drop Table MyTable"; oracleCommand1.ExecuteNonQuery(); } catch(Exception ex1) { MessageBox.Show("Исключение:\n"+ex1.Message); } try { oracleCommand1.CommandText = "create table MyTable(col1 number, col2 BFILE)"; oracleCommand1.ExecuteNonQuery(); } catch(Exception ex2) { MessageBox.Show("Исключение:\n"+ex2.Message); } oracleCommand1.CommandText = "insert into MyTable values ('1', BFILENAME('MYDIR', 'a.bmp'))"; oracleCommand1.ExecuteNonQuery(); oracleCommand1.CommandText = "insert into MyTable values ('2',BFILENAME('MYDIR','a.jpg'))"; oracleCommand1.ExecuteNonQuery(); oracleCommand1.CommandText = "insert into MyTable values ('3',BFILENAME('MYDIR','a.gif'))"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { oracleConnection1.Close(); } break; case 2: byte[] buffer =null; try { oracleConnection1.Open(); oracleCommand1.CommandText = "select * from MyTable"; System.Data.OracleClient.OracleDataReader myReader = oracleCommand1.ExecuteReader(); using(myReader) { while(myReader.Read()) { System.Data.OracleClient.OracleBFile myBFile = myReader.GetOracleBFile(1); buffer=new byte[myBFile.Length]; using(myBFile) { myBFile.Seek(0, SeekOrigin.Begin); myBFile.Read(buffer, 0, (int)myBFile.Length); MemoryStream memorystream = new MemoryStream(buffer); pictureBox1.Image=Image.FromStream(memorystream); MessageBox.Show("Отображена картинка: "+myBFile.FileName); } } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { oracleConnection1.Close(); } break; Использование типа данных RAW и LONG RAWПринцип работы с данными типа RAW и LONG RAW полностью аналогичен работе с Lob типами, за исключением использования класса данных OracleBinary. Покажем это на примере записи в таблицу базы данных текста из файла a.txt и картинки из файла a.jpg (файлы уже использованы выше) и отображения записанной в таблице информации. case 1: byte[] buffer=null; OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER,col2 RAW(200),col3 LONG RAW)"; myCommand.ExecuteNonQuery(); string connectionstring="user id=myUser;data source=mySource; password=myPassword"); OracleDataAdapter myDataAdapter = new OracleDataAdapter("SELECT col1,Col2,Col3 FROM MyTable", connectionstring); DataTable datatable = new DataTable(); myDataAdapter.FillSchema(datatable, SchemaType.Source); OracleCommandBuilder mycommandbuilder = new OracleCommandBuilder(myDataAdapter); DataRow datarow = datatable.NewRow(); datarow[0]=1; using(FileStream filestream = new FileStream(@"C:\a.txt",FileMode.Open)) { buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); datarow[1]=buffer; } int viLength=0; using(FileStream filestream= new FileStream(@"C:\a.jpg",System.IO.FileMode.Open)) { viLength=(int)filestream.Length; buffer=new byte[filestream.Length]; filestream.Read(buffer,0,(int)filestream.Length); } datarow[2]=buffer; datatable.Rows.Add(datarow); myDataAdapter.Update(datatable); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; case 2: OracleConnection myConnection = new OracleConnection(myconnectionstring); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText="select col1,col2,col3 from mytable"; byte[] buffer=null; string s=null; try { OracleDataReader myReader = myCommand.ExecuteReader(); using(myReader) { while(myReader.Read()) { OracleBinary oraclebinary = myReader.GetOracleBinary(1); using(MemoryStream memorystream = new MemoryStream()) { memorystream.Write((byte[])oraclebinary,0,oraclebinary.Length); s=System.Text.Encoding.Default. GetString((byte[])oraclebinary).Trim(); } oraclebinary= myReader.GetOracleBinary(2); using(MemoryStream memorystream = new MemoryStream()) { memorystream.Write((byte[])oraclebinary,0,oraclebinary.Length); //Отображаем картинку pictureBox1.Image=Image.FromStream(memorystream); } MessageBox.Show("Отображена картинка номер "+ Convert.ToString(myReader["col1"])+ "\n Текст RAW: "+ s); } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Результат выполнения кода представлен на Рис.4.
Рис.4 Работа с RAW данными Работа с типом данных IntervalРабота с типом данных Interval полностью аналогична работе с другими типами данных (LOB, RAW). Здесь, также как и при работе с Lob типами, требуется получить схему данных, для того, чтобы, создав виртуальную запись в таблице, созданной с использованием полученной схемы, можно было присвоить соответствующего типа значения столбцам и, далее, перенести изменения в таблицу базы данных. При этом используются структуры OracleMonthSpan и OracleTimeSpan, и методы GetOracleMonthSpan() и GetOracleTimeSpan() OracleDataReader для кодирования интервалов. case 1: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER, col2 INTERVAL YEAR TO MONTH,col3 INTERVAL DAY TO SECOND)"; myCommand.ExecuteNonQuery(); string connectionstring="user id=scott;data source=mybase;password=mypasword"; OracleDataAdapter myDataAdapter = new OracleDataAdapter("SELECT col1,Col2,Col3 FROM MyTable", connectionstring); DataTable datatable = new DataTable(); myDataAdapter.FillSchema(datatable, SchemaType.Source); OracleCommandBuilder mycommandbuilder = new OracleCommandBuilder(myDataAdapter); DataRow datarow = datatable.NewRow(); int col1 = 1; //Интервал 1 год 2 месяца OracleMonthSpan col2 = new OracleMonthSpan(1,2); // 1 день, 2 часа , 3 минуты, 4 секунды, 5 миллисекунд OracleTimeSpan col3 = new OracleTimeSpan(1,2,3,4,5); datarow[0] = col1; datarow[1] =col2.Value; datarow[2] =col3.Value; datatable.Rows.Add(datarow); myDataAdapter.Update(datatable); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; case 2: string connectionstring="user id=scott;data source=mybase;password=mypasword"; OracleConnection myConnection = new OracleConnection(myconnectionstring); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText="select col1,col2,col3 from mytable"; OracleDataReader myReader = myCommand.ExecuteReader(); OracleMonthSpan monathspan; OracleTimeSpan timespan; try { using(myReader) { while(myReader.Read()) { monathspan=myReader.GetOracleMonthSpan(1); timespan=myReader.GetOracleTimeSpan(2); MessageBox.Show("Интервал в месяцах: "+monathspan.ToString()+ "\nИнтервал часы, минуты, секунды, милисекунды :"+ timespan.ToString()+")"); } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } brerak;
Рис.5 Работа с Interval типом данных Использование типа данных RowIdЗначение псевдостолбца ROWID уникално для любой строки в пределах таблицы и остается постоянным на протяжении всего времени жизни строки. Однако ROWID не тождественен ключу, так как он может меняться при удалении и повторном добавлении строки. Кроме того, после удаления некоторой строки ORACLE назначит этот ROWID вновь добавленной строке. Значение ROWID может быть прочитано предложением SELECT, но не может быть записан в базу данных (невозможно выполнить для ROWID операции UPDATE, INSERT или DELETE). Для IOT (index-organized table) существует аналог ROWID - UROWID. Физически ROWID это адрес записи, созданный по принципу номер сегмента БД + номер датафайла + номер блока данных + номер строки в блоке. Потому, что ROWID является физическим адресом строки, он позволяет обеспечить самый быстрый доступ к строке данных. Исходя из этого, можно использовать RowId для сокращения время доступы к многократно используемым на протяжении одной задачи строкам данных. case 1: OracleString myRowId=null; OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText="select rowid,EMPNO from emp where EMPNO=7900"; OracleDataReader myReader = myCommand.ExecuteReader(); //Запоминаем ROWID using(myReader) { myReader.Read(); myRowId=myReader.GetOracleString(0); } ......... //Используем полученное значение ROWID myCommand.CommandText="select * from emp where ROWID='"+ myRowId.ToString()+"'"; myReader = myCommand.ExecuteReader(); using(myReader) { myReader.Read(); MessageBox.Show("Имя : "+myReader.GetString(1)+ "\nПрофессия: "+myReader.GetString(2)); } myConnection.Close(); break; Использование типов данных для работы с датами и временемПодробно о данных типа DateTime, Timestamp, TimestampLocal, TimestampWithTZ мы уже говорили выше, поэтому здесь приведем лишь пример кода, результаты работы которого показаны на Рис.6. case 1: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER, col2 DATE, col3 TIMESTAMP(9), col4 TIMESTAMP(9) WITH TIME ZONE, col5 TIMESTAMP(9) WITH LOCAL TIME ZONE)"; myCommand.ExecuteNonQuery(); DateTime datetime = DateTime.Now; myCommand.CommandText = "INSERT INTO MyTable VALUES(" +1+ ",TO_DATE('"+datetime+"','DD.MM.YYYY HH24:MI:SS'),'"+ datetime.ToString("dd.MM.yyyy hh:mm:sstt") + "', '" + datetime.ToString("dd MMM yyyy hh:mm:sstt") + "', '" + datetime.ToString("dd MMM yyyy hh:mm:sstt") + "')"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); myConnection.Close(); } finally { myConnection.Close(); } break; case 2: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText="select * from mytable"; OracleDataReader myReader = myCommand.ExecuteReader(); try { using(myReader) { while(myReader.Read()) { MessageBox.Show("Date:\t\t"+myReader.GetOracleDateTime(1)+ "\nTimestamp:\t"+myReader.GetOracleDateTime(2).ToString()+ "\nTimestampLocal:\t"+myReader.GetOracleDateTime(3).ToString()+ "\nTimestampWithTZ:\t"+myReader.GetOracleDateTime(4).ToString()); } } } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break;
Рис.6 Работа с типами данных DateTime, Timestamp, TimestampLocal, TimestampWithTZ URI типы данных и возможности их использования в C#URI (Universal Resource Identifiers - универсальные идентификаторы ресурсов) внешних Web-страниц и файлов, а также для обращения к данным, хранящимся в базе данных. Oracle9i поддерживает UriType данных, которые могут использоваться, для хранения запросов Uri-refs внутри базы данных. UriType - это абстрактный тип, подтипами которого являются HttpUriType, XDBUriType и DBURITYPE . В базе данных возможно создать столбец UriType и хранить данные DBURITYPE, XDBUriType или HttpUriType. Кроме того, допускается создание собственных подтипов UriType для обработки других протоколов URL. Для работы с типами данных URI Oracle содержит пакет URIFACTORY, который может создавать и возвращать различные подтипы URI типов. Пакет анализирует строку URL, опознает тип URL (HTTP, DBURI, и так далее), и создает экземпляр подтипа. Чтобы создать DBURI подтип, URL должен начаться с приставки /oradb. Например, URIFactory.getURI (/oradb/SCOTT/EMP) создаст DBURITYPE, а URIFACTORY.GETURI ('/sys/schema ') создаст XDBURITYPE. Чтобы работать с нестандартными типами данных они должны быть зарегестрированы в Oracle. Доступ непосредственно к данным выполняются методами getBlob(), getClob(), getContentType(), getExternalUrl(), getUrl(), getXML(). Следующий пример показывает, как можно создать таблицу со столбцами Uri типов данных, как записать ссылки в созданную таблицу базы данных на web страницы и на строки других таблиц. После выаолнения операций записи в case 2 проводится выборка данных по ссылкам и их сохранение в файлах. case 1: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER, col2 SYS.URITYPE, col3 SYS.HTTPURITYPE,col4 SYS.DBURITYPE)"; myCommand.ExecuteNonQuery(); //Использование URIFACTORY для сохранения данных myCommand.CommandText = "INSERT INTO MyTable VALUES(" +1+ ", SYS.URIFACTORY.GETURI('http://wladm.narod.ru')" + ", SYS.HTTPURITYPE('wladm.narod.ru')" + ", SYS.DBURITYPE('/SCOTT/EMP/ROW[ENAME=\"SMITH\"]'))"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); myConnection.Close(); } finally { myConnection.Close(); } break; case 2: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText= "select e.col1,"+ "e.col2.getUrl(),"+ //Выбор ссылок "e.col3.getUrl(),"+ "e.col4.getUrl(),"+ // Выбор данных по ссылкам "e.col2.getCLob(),"+ "e.col3.getCLob(),"+ "e.col4.getCLob() from mytable e";//3 try { using(OracleDataReader myReader = myCommand.ExecuteReader()) { while(myReader.Read()) { //Чтение строк со ссылками string s1=myReader.GetString(1); string s2=myReader.GetString(2); string s3=myReader.GetString(3); //Чтение данных OracleLob clob1 =myReader.GetOracleLob(4); OracleLob clob2 =myReader.GetOracleLob(5); OracleLob clob3 =myReader.GetOracleLob(6); //Выводим содержимое таблицы Oracle Рис.7. MessageBox.Show( "URITYPE:\t\t"+s1+ "\nHTTPURITYPE:\t"+s2+ "\nDBURITYPE:\t"+s3); //Выводим в файл C:\a1.html страничку сайта int iTextLength = System.Text.Encoding.Default.GetByteCount(clob1.Value.ToString()); string path1 = @"C:\a1.html"; using(MemoryStream memorystream=new MemoryStream()) { memorystream.SetLength(iTextLength); memorystream.Write(System.Text.Encoding.Default. GetBytes(clob1.Value.ToString()),0,iTextLength); using(StreamWriter streamwriter = new StreamWriter(path1)) { //Переносим в файл streamwriter.Write(System.Text.Encoding.Default. GetChars(memorystream.ToArray())); } } //В файл C:\a2.html string path2 = @"C:\a2.html"; iTextLength =System.Text.Encoding.Default. GetByteCount(clob2.Value.ToString()); using(MemoryStream memorystream=new MemoryStream()) { memorystream.SetLength(iTextLength); memorystream.Write(System.Text.Encoding.Default. GetBytes(clob2.Value.ToString()),0,iTextLength); using(StreamWriter streamwriter = new StreamWriter(path2)) { //Переносим в файл streamwriter.Write(System.Text.Encoding.Default. GetChars(memorystream.ToArray()) { streamwriter.Write(System.Text.Encoding.Default. GetChars(memorystream.ToArray())); } } } string path3 = @"C:\a3.xml"; iTextLength =System.Text.Encoding.Default. GetByteCount(clob3.Value.ToString()); using(MemoryStream memorystream=new MemoryStream()) { memorystream.SetLength(iTextLength); memorystream.Write(System.Text.Encoding. Default.GetBytes(clob3.Value.ToString()),0,iTextLength); using(StreamWriter streamwriter = new StreamWriter(path3)) { streamwriter.Write(System.Text.Encoding.Default. GetChars(memorystream.ToArray())); } } }//while(myReader.Read()) }//using(OracleDataReader myReader } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break;
Рис.7 URI типы. Хранение в таблице БД На Рис.8. показан файл, сформированный с использованием DBUriType и его отображение в Internet Explorer. Файлы html, копии странички http://wladm.narod.ru здесь не приводятся, однако следует отметить, что Encoding может быть другим (зависит от настроек Oracle).
Рис.8 Файл, сформированный с использованием DBUriType Хранение XML типов в OracleБольшинство баз данных обладают реляционной структурой, а документы XML - иерархической. По данной причине используется два подхода к хранению XML документов:
При первом способе теряется гарантия сохранения идентичности документа оригиналу, так как некоторые элементы (такие как комментарии и вспомогательные инструкции) будут потеряны при размещении данных в таблицы базы данных. Однако этот способ позволяет работать с данными с использованием SQL предложений. Второй способ гарантирует сохранение структуры документа и его полную идентичность оригиналу, но с этими данными нельзя работать с использованием SQL-запросов. Для данных формата XML в СУБД Oracle введен собственный XMLType, как объектный тип Oracle и новый, определяемый пользователем, тип данных, который позволяет абстрагироваться от используемой модели хранения. Он предлагает возможности как CLOB, так и объектно-реляционное представление "разложенного" (по таблице базы данных) XML-документа. Работу с Lob типами мы уже рассматривали, поэтому ниже приводятся примеры, в которых рассматривается работа с использованием XMLType данных Oracle. Запись данных XML в Oracle и доступ к даннымВ приведенном ниже коде в case 1 вначале создается таблица с типом данных столбца col2 SYS.XMLTYPE. Далее создается XML документ (Документ 1). Документ имеет Id для значения узла Record. Документ записывается в файл (для показа возможности записи документа из файла в БД), затем данные из файла переносятся в таблицу БД в столбец с типом данных XMLTYPE. Аналогично создается и заносится в файл Документ 2 и добавляется в таблицу Oracle. Документ 1 <?xml version="1.0" encoding="windows-1251"?> <NoteBook> <Record ID="1">Name: Wlad. Phone: 5555555</Record> </NoteBook> Документ 2 <?xml version="1.0" encoding="windows-1251"?> <NoteBook> <Record ID="2">Name: Peter. Phone: 7777777</Record> </NoteBook> Код создания и добавления документов XML в таблицу БД: case 1: //Удаление старой и создание новой таблицы OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { //Таблица с типом данных XML myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER, col2 SYS.XMLTYPE)"; myCommand.ExecuteNonQuery(); //Создаем первый документ XmlDocument xmlDocument = new XmlDocument( ); XmlDeclaration xmlDeclaration = xmlDocument.CreateXmlDeclaration("1.0","windows-1251", null); xmlDocument.InsertBefore(xmlDeclaration, xmlDocument.DocumentElement); //Root узел XmlElement notebook = xmlDocument.CreateElement("NoteBook"); xmlDocument.AppendChild(notebook); XmlElement rec1 = xmlDocument.CreateElement("Record"); XmlAttribute newattr1 = xmlDocument.CreateAttribute("ID"); //ID Record = 1 newattr1.Value=Convert.ToString(1); rec1.SetAttributeNode(newattr1); rec1.InnerText="Name: Wlad. Phone: 5555555"; notebook.AppendChild(rec1); //Сохраняем документ в файле xmlDocument.Save(@"C:\01.xml"); string path=@"C:\01.xml"; string s=null; //Читаем Документ 1 using(TextReader streamreader = new StreamReader(path)) { s=streamreader.ReadToEnd(); } //Вставляем в таблицу первой записью myCommand.CommandText = "INSERT INTO myTable(col1,col2) values (1,SYS.XMLTYPE.CREATEXML('" + s + "'))"; myCommand.ExecuteNonQuery(); xmlDocument = new XmlDocument( ); //Создаем второй документ xmlDeclaration = xmlDocument.CreateXmlDeclaration("1.0", "windows-1251", null); xmlDocument.InsertBefore(xmlDeclaration, xmlDocument.DocumentElement); //Root узел notebook = xmlDocument.CreateElement("NoteBook"); xmlDocument.AppendChild(notebook); rec1 = xmlDocument.CreateElement("Record"); //ID узла Record 2 newattr1 = xmlDocument.CreateAttribute("ID"); newattr1.Value=Convert.ToString(2); rec1.SetAttributeNode(newattr1); rec1.InnerText="Name: Peter. Phone: 7777777"; notebook.AppendChild(rec1); xmlDocument.Save(@"C:\01.xml"); //Читаем файл using(TextReader streamreader = new StreamReader(path)) { s=streamreader.ReadToEnd(); } //Переносим данные в таьлуцу БД myCommand.CommandText = "INSERT INTO myTable(col1,col2) values (1, SYS.XMLTYPE.CREATEXML('" + s + "'))"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Далее показаны возможности отображения документа: 1. Выбор строк данных из таблицы: case 2: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText= "SELECT col1, SYS.XMLTYPE.GETSTRINGVAL(col2) FROM MyTable"; try { using(OracleDataReader myReader = myCommand.ExecuteReader()) { while(myReader.Read()) { //Чтение строк со ссылками string s1=myReader.GetString(1); MessageBox.Show("XML:\t\t"+s1); }//while(myReader.Read()) }//using(OracleDataReader myReader } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Код примера поочередно выведет все записи из таблицы (их у нас две), пример вывода показан на Рис.9.
Рис.9 Доступ к данным XMLTYPE 2. Доступ с использованием индексов узлов: Заменим в коде предыдущего примера две строчки, и выведена будет только одна запись (Рис.9): myCommand.CommandText= "SELECT t.col2.XMLDATA FROM MYTABLE t WHERE t.COL2.EXISTSNODE('/NoteBook/Record[attribute::ID=1]')=1"; tring s1=myReader.GetString(0); 2. Доступ к значению записи: Следующая замена в пункте два позволяет получить значение узла (Рис.10): myCommand.CommandText= "SELECT EXTRACTVALUE(t.COL2,'/NoteBook/Record') FROM MYTABLE t WHERE t.COL2.EXISTSNODE('/NoteBook/Record[attribute::ID=2]')=1";
Рис.10 Доступ к значению данных XMLTYPE Описанный выше метод вновь ограничен возможной длиной предложения Insert в 4000 байт и, поэтому, для записи больших XML файлов либо необходимо прибегать к декомпозициии файла, либо использлвать типы данных CLOB или NCLOB. Преобразование данных таблиц в XML документыДля преобразования данных таблиц в стандарте SQL:2003 разработан ряд функций SQL/XML (или SQLX). В настоящее время Oracle поддерживаются не все функции. Эти функции (такие как: XMLElement, XMLAttributes, XMLAgg,XMLCOLATTVAL, XMLConcat, XMLForest ...), могут быть использованы в предложениях Select. Рассмотрим несколько примеров использования функций (поддерживаемых на доступном автору сервере). В следующем примере выводится значение таблицы Emp, как совокупности сформированных строк xml документа (Рис.11): case 2: OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); //Только TO_CLOB, TO_CHAR не может быть использован myCommand.CommandText= "SELECT to_clob(XMLELEMENT(EMP, XMLFOREST(empno, ename, job,sal,deptno))) FROM emp"; try { using(OracleDataReader myReader = myCommand.ExecuteReader()) { while(myReader.Read()) { string s=myReader.GetString(0); MessageBox.Show("XML:\t\t"+s); }//while(myReader.Read()) }//using(OracleDataReader myReader } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break;
Рис.11 Преобразование и вывод данных из таблиц Oracle в виде XML данных Следующая замена команды позволяет выполнить вывод несколько в другом аспекте(Рис.12): myCommand.CommandText= "SELECT to_clob(XMLELEMENT(Emp, XMLATTRIBUTES(ename AS \"Name\", empno AS \"Number\", sal as \"SAL\",job AS \"JOB\", deptno AS \"DEPTNO\"))) FROM emp";
Рис.12 Преобразование и вывод данных из таблиц Oracle в виде XML данных: Следующая замена команды позволяет выполнить вывод c использованием группировки данных. Агрегирующая функция XMLAGG может использоваться в запросах с группировкой GROUP BY для данных XMLTYPE, подобно тому, как агрегирующие функции MIN, AVG и другие для обычных данных (Рис.13): myCommand.CommandText= "SELECT to_clob(XMLELEMENT(DEPT, XMLATTRIBUTES(deptno AS no))), to_clob(XMLAGG(XMLELEMENT(EMP, ename))) FROM emp GROUP BY deptno"; ..... string s=myReader.GetString(0); string s1=myReader.GetString(1); MessageBox.Show("XML:\t\t"+s+"\t"+s1);
Рис.13 Преобразование и вывод данных из таблиц Oracle в виде XML данных: Результат использования функции XMLCOLATTVAL показан на Рис.14. Соответственно изменим CommandText и вывод вернем для одного столбца: myCommand.CommandText= "SELECT to_clob(XMLELEMENT(Emp,XMLCOLATTVAL(e.ename, e.job, e.sal))) \"Emp Element\" FROM emp e"; string s=myReader.GetString(0); MessageBox.Show("XML:\t\t"+s);
Рис.14 Преобразование и вывод данных из таблиц Oracle в виде XML данных: Работа с типами данных, определяемых пользователемПриводимый ниже пример показывает все особенности работы User Defined типом данных. Практически мы должны лишь зарегистрировать тип данных в Oracle и далее возможно его использование. Отметим, что типы данных, входящих в состав сборки типов пользователя, должны иметь определенный размер. Так в примере - если Description задать как VARCHAR, то тип данных не будет зарегистрирован. OracleConnection myConnection = new OracleConnection("user id=myUser;data source=mySource; password=myPassword"); case 1: myConnection.Open(); OracleCommand myCommand = myConnection.CreateCommand(); try { myCommand.CommandText = "drop Table MyTable"; myCommand.ExecuteNonQuery(); } catch(Exception ex1) { ; } try { //Регистрируем наш тип данных myCommand.CommandText = "CREATE OR REPLACE TYPE MyType IS OBJECT (id number, Description VARCHAR(20))"; myCommand.ExecuteNonQuery(); //Создаем таблицу с созданным типом данных myCommand.CommandText = "CREATE TABLE MyTable (col1 NUMBER, col2 MyType)"; myCommand.ExecuteNonQuery(); //Вставляем записи myCommand.CommandText = "INSERT INTO MyTable VALUES(1,MyType(20,'Запись 1'))"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO MyTable VALUES(1,MyType(10,'Запись 2'))"; myCommand.ExecuteNonQuery(); } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; case 2: OracleCommand myCommand = myConnection.CreateCommand(); myCommand.CommandText ="SELECT col1, t.col2.Id, t.col2.description FROM MyTable t"; myConnection.Open(); try { using(OracleDataReader myReader = myCommand.ExecuteReader()) { while(myReader.Read()) { int i=myReader.GetInt32(1); string s=myReader.GetString(2); MessageBox.Show("ID UserDefinedType:\t"+ Convert.ToString(i)+ "\tЗапись поля:" +s); }//while(myReader.Read()) }//using(OracleDataReader myReader } catch(Exception ex) { MessageBox.Show("Исключение:\n"+ex.Message); } finally { myConnection.Close(); } break; Результат работы кода представлен на Рис.15.
Рис.15 Работа с типами данных, определяемых пользователем Вместо заключенияНа этом мы заканчиваем рассмотрение вопросов "Особенности использования провайдера данных для Oracle" и "Типы данных Oracle и особенности их применения". Относительно типов данных. Если учесть, что работу с курсорами мы уже рассмотрели до этого (см. параграф "Использование пакетного заполнения DataSet"), а простые типы данных (varchar, int, char...) так или иначе использовались при рассмотрении работы с другими типами данных, то в основном нами охвачена работа с практически всеми, используемыми в Oracle типами данных. Использования провайдера данных для Oracle, практически аналогично использованию провайдера данных MS SQL серверу. В силу этого можно утверждать, что задача, поставленная в начале параграфа, нами выполнена. Автор обещает, что, по мере решения в своей практической работе задач, которые не описаны в данном материале, он будет пополняться.
Литература: Bill Hamilton "Advanced Data Access with ADO.NET and Oracle" August 2004 Еcли Вы пришли с поискового сервера - посетите мою главную страничкуНа главной странице Вы найдете программы комплекса Veles - для тех кто готовится к экзаменам на право управления автомобилем или мотоциклом, программу NumberPhoto, созданную для работы с фото, сделанными цифровым фотоаппаратом, программу Bricks - игрушку для детей и взрослых, программу записную книжку, теоретический материал по программированию в среде Borland C++ builder, C# (C .Net). |