? Особенности работы с Oracle в Visual Studio 2003 (ASP.NET 1)

Особенности работы с Oracle в Visual Studio 2003 (ASP.NET 1)


В начало

Вместо предисловия

Данный материал написан для корпоративного использования. В силу этого, основное внимание сосредоточено на вопросах, которые, так или иначе, интересны автору для повседневной работы. Но, так как материал, помещенный в Inet, имеет тенденцию развиваться далее, за счет критических замечаний и дополнений, которые, хотя и редко (по опыту опубликованных материалов по Builder), но иногда все же присылаются автору, то я решил поделиться этим материалом с Вами. Жду замечаний и пожеланий, материалов с дополнениями и т.п. (адрес wladm@narod.ru).

Для рассмотрения данного материала создадим новый проект (Рис.1) с именем AppBD, с панелью инструментов на базе контрола ToolBar с тремя кнопками. (Подробно о создании проектов решения см. в разделе "Простейшие Windows Application решения").

bd0101.gif

Рис.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 для Oracle

OracleConnection, 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.

bd0302.gif

Рис.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;


В начало

Особенности использования OracleCommand

1. В отличие от объекта Command других провайдеров данных .NET Framework (SQL Server, OLE DB и ODBC) объект OracleCommand не поддерживает свойство CommandTimeout. Задание времени ожидания для команды бесполезно, а возвращаемое значение всегда равно нулю.

2. OracleCommand, помимо методов ExecuteReader, ExecuteNonQuery, ExecuteScalar имеет два спицифических метода:

  • ExecuteOracleNonQuery - выполняет инструкцию SQL для Connection и возвращает для UPDATE, INSERT и DELETE число измененных строк. Для прочих SQL предложений возвращаемым значением является -1.

  • ExecuteOracleScalar - извлекает одинарное значение (например, итоговое значение) с типом данных Oracle из базы данных. Данный метод позволяет извлечь данные не используя DataReader.

Для демонстрации возможностей методов, поместим на заготовку формы (Рис.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 отметим:

  • Cancel - делает попытку отменить выполнение команды. Если отменить не удалось - ничего не происходит;

  • Clone - создает копию объекта OracleCommand;

  • CreateParameter - создает новый экземпляр объекта OracleParameter.

  • Prepare - создает подготовленную для выполнения (скомпилированную) версию команды в источнике данных.


В начало

Типы данных Oracle и особенности их использования


В начало

Типы данных .NET Framework для работы с Oracle

Тип данных Является типом данных Oracle Описание Тип в OracleParameter.Value
Any Да, нет Предназначен для хранения явно определенных типов данных. В столбце этого типа в каждой строке могут храниться данные разных типов (в одной строке - символьные, в другой - числовые, в третьей - структура...). Описание типа данных в поле каждой конкретной строки хранится в самом поле.
BFile Да Тип данных Oracle, содержащий в таблице базы данных не сами данные, а ссылку на файл данных, который располагается в файлах ОС.   OracleClient OracleBFile
Blob Да Тип данных Oracle LOB, содержащий двоичные неструктурированные данные максимального размера 4 гигабайта.   OracleClient OracleLob
Byte Нет Тип данных для представления 8-разрядных целых чисел без знака, в диапазоне от 0 до 255.   .NET Framework Byte
Char Да Тип данных, содержащий строку знаков фиксированной длины с максимальным размером 2000 байт.   .NET Framework String или
  OracleClient OracleString
Clob Да Тип данных, содержащий символьные данные. Основан на стандартном наборе знаков сервера. Максимальный размер - 4 гигабайта.   OracleClient OracleLob
Cursor Да Тип данных Oracle REF CURSOR -PL/SQL указатель на SQL курсор, возвращающий строку таблицы базы данных.   OracleDataReader недоступен
DateTime Да Тип данных Oracle DATE, содержащий представление даты и времени фиксированной длины, с 01.01.4712 г. до н. э. по 31.12.4712 г. н. э. Для дат н. э. он отображается в тип данных DateTime. Чтобы связать даты до н. э., следует использовать параметр String и функции преобразования Oracle TO_DATE или TO_CHAR для входных и выходных параметров соответственно.   .NET Framework DateTime или
  OracleClient OracleDateTime.
Double Нет Значение двойной точности с плавающей точкой.   .NET Framework Double или
  OracleClient OracleNumber
Float Нет Значение одинарной точности с плавающей точкой.   .NET Framework Single или
  OracleClient OracleNumber
Int16 Нет Целый тип для представления 16-разрядных целых чисел со знаком, находящихся в диапазоне от -32768 до 32767.   .NET Framework Int16 или
  OracleClient OracleNumber
Int32 Нет Целый тип для представления 32-разрядных чисел со знаком, находящихся в диапазоне от -2147483648 до 2147483647.   .NET Framework Int32 или
  OracleClient OracleNumber.
IntervalDayToSecond Да Тип данных Oracle INTERVAL DAY TO SECOND (Oracle версии 9i или более поздней версии) содержит интервал времени в днях, часах, минутах и секундах и имеет фиксированный размер 11 байтов. Это тип данных для хранения интервалов времени, не привязанных к конкретной дате   NET Framework TimeSpan или
  OracleClient OracleTimeSpan.
IntervalYearToMonth Да Тип данных Oracle INTERVAL YEAR TO MONTH (Oracle версии 9i или более поздней версии) содержит интервал времени в годах и месяцах, имеющий фиксированный размер 5 байтов. Это тип данных для хранения интервалов времени, не привязанных к конкретной дате   .NET Framework Int32 или
  OracleClient OracleMonthSpan.
LongRaw Да Тип данных Oracle LONGRAW, содержащий двоичные данные переменной длины с максимальным размером 2 гигабайта. Данные не интерпретируется Oracle.   .NET Framework Byte[] или
  OracleClient OracleBinary
LongVarChar Да Тип данных Oracle LONG, содержащий строку знаков переменной длины с максимальным размером 2 гигабайта.   NET Framework String или
  OracleClient OracleString.
NChar Да Тип данных Oracle NCHAR, содержащий строку знаков фиксированной длины для хранения в национальном наборе знаков в базе данных максимального размера 2000 байтов (а не знаков). Размер значения зависит от национального набора знаков в базе данных.   .NET Framework String или
  OracleClient OracleString
NClob Да Тип данных Oracle NCLOB, содержащий символьные данные для хранения в национальном наборе знаков в базе данных максимального размера 4 гигабайта (а не знака). Размер значения зависит от национального набора знаков в базе данных.   OracleLob
Number Да Тип данных Oracle NUMBER, содержащий числовые данные переменной длины с максимальной точностью и масштабом 38. Он отображается в тип данных Decimal. Чтобы выполнить привязку типа данных Oracle NUMBER, который превышает содержимое Decimal.MaxValue, следует использовать либо тип данных OracleNumber, либо параметр String и функции преобразования Oracle TO_NUMBER или TO_CHAR для входных и выходных параметров соответственно.   .NET Framework Decimal или
  OracleClient OracleNumber.
NVarChar Да Тип данных Oracle NVARCHAR2, содержащий строку знаков переменной длины для хранения в национальном наборе символов в базе данных с максимальным размером 4000 байтов (а не знаков). Размер значения зависит от национального набора знаков в базе данных.   NET Framework String или
  OracleClient OracleString
Raw Да Тип данных Oracle RAW, содержащий двоичные данные переменной длины с максимальным размером 2000 байтов. Данные не интерпретируется Oracle.   .NET Framework Byte[] или
  OracleClient OracleBinary.
RowId Да Каждая таблица Oracle имеет псевдостолбец по имени ROWID - последовательность символов, которая уникально идентифицирует строку и, таким образом, представляет ее физический адрес. Рассматривается как псевдостолбец. Идентификаторы строк (ROWID) можно извлекать в запросе с помощью зарезервированного слова ROWID. Псевдостолбцы не хранятся в таблицах базы данных, и не могут быть изменены или удалены.   .NET Framework String или
  OracleClient OracleString
SByte Нет Целый тип для представления 8-разрядных чисел со знаком, находящихся в диапазоне от -128 до 127.   .NET Framework SByte или
  OracleClient OracleTimeSpan.
Timestamp Да Тип данных Oracle TIMESTAMP (Oracle 9i или более поздней версии), содержащий дату и время (включая секунды), с размером в диапазоне от 7 до 11 байтов. TimeStamp тип данных, который позволяет хранить в базе данных дату с точностью до долей секунды (до 9 знаков после запятой).   .NET Framework DateTime или
  OracleClient OracleDateTime
TimestampLocal Да Тип данных Oracle TIMESTAMP WITH LOCAL TIMEZONE (Oracle 9i или более поздней версии), содержащий дату, время и ссылку на исходный часовой пояс, с размером в диапазоне от 7 до 11 байтов.   .NET Framework DateTime или
  OracleClient OracleDateTime
TimestampWithTZ Да Тип данных Oracle TIMESTAMP WITH TIMEZONE (Oracle 9i или более поздней версии), содержащий дату, время и определенный часовой пояс и имеющий фиксированный размер 13 байтов. Сравнение данных этого типа осуществляется с учетом часового пояса. Cписок стандартных часовых поясов в Oracle9i охватывает весь земной шар.   .NET Framework DateTime или
  OracleClient OracleDateTime
UInt16 Нет Целый тип для представления 16-разрядных целых чисел без знака, находящихся в диапазоне от 0 до 65535.   .NET Framework UInt16 или
  OracleClient OracleNumber
UInt32 Да Целый тип для представления 32-разрядных целых чисел без знака, находящихся в диапазоне от 0 до 4294967295.   .NET Framework UInt32 или
  OracleClient OracleNumber.
URI Да Компактная строка символов для идентификации абстрактного или физического ресурса. Значением URI может быть или URL или URN. URL и URN представляют собой указатели на конкретные сущности, которые действительно существуют. Oracle может создавать и возвращать различные подтипы URI типов.
User-defined Да Комплексный тип данных, созданный на основных типов данных Оракла и определяемых пользователем.   
VarChar Да Тип данных Oracle VARCHAR2, содержащий строку знаков переменной длины с максимальным размером 4000 байтов.   .NET Framework String или
  OracleClient OracleString.
XML Да Тип данных XML. .NET Framework String

Пространство имен System.Data.OracleClient соответственно имеет и классы и структуры для поддержки специфических типов данных, это:

  • OracleBFile - Класс, представляющий Oracle BFILE тип данных.

  • OracleBinary - Структура, которая представляет поток двоичных данных переменной длины.

  • OracleBoolean - Структура, которая представляет значение, возвращенное от сравнения базы данных между типами данных Oracle. Структура OracleBoolean имеет следующие возможные значения: True, False, Null, One и Zero.

  • OracleDateTime - Предоставляет данные даты и времени в диапазоне от 1 января 4712 г. до н. э. до 31 декабря 4712 г. н. э. Минимальная допустимая дата для структуры OracleDateTime - 01.01.0001 00:00:00. Однако Oracle поддерживает даты начиная с 1 января 4712 г. до н. э. Чтобы привязать даты до н. э., следует использовать параметр String и функции преобразования Oracle TO_DATE или TO_CHAR для входных и выходных параметров соответственно. Чтобы получить объект OracleDateTime, следует вызвать метод GetOracleDateTime.

  • OracleLob - класс, представляющий большие двоичные объекты (LOB), сохраненные в базе данных Oracle.

  • OracleMonthSpan - Предоставляет интервал времени в месяцах и соответствует типу данных Oracle 9i INTERVAL YEAR TO MONTH. Тип данных Oracle INTERVAL YEAR TO MONTH (Oracle версии 9i или более поздней версии) содержит интервал времени в годах и в месяцах, имеющий фиксированный размер 5 байтов. Чтобы получить объект OracleMonthSpan, следует вызвать метод GetOracleMonthSpan.

  • OracleNumber - Структура, предоставляющая для хранения или извлечения из базы данных значений с фиксированной точностью и числовым масштабом между -10 в степени 38 -1 и 10 в степени 38 -1.

  • OracleString - структура, gредоставляет поток знаков переменной длины, который нужно сохранить в базе данных или извлечь из нее.

  • OracleTimeSpan - структура. Предоставляет интервал времени и соответствует типу данных Oracle 9i INTERVAL DAY TO SECOND.

  • OracleType - перечисление типов данных Oracle. Задает тип данных поля или свойство для использования в OracleParameter.

Тип данных Метод доступа к данным
OracleBFile GetOracleBFile
OracleBinary GetOracleBinary
OracleBoolean  
OracleDateTime GetOracleDateTime
OracleLob GetOracleLob
OracleMonthSpan GetOracleMonthSpan
OracleNumber GetOracleNumber
OracleString GetOracleString
OracleTimeSpan GetOracleTimeSpan


В начало

Работа с 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 поддерживает следующие четыре типа для больших объектов:

  • BFILE - для внешнего двоичного файла;

  • BLOB - для внутреннего двоичного объекта;

  • CLOB - для внутреннего символьного объекта;

  • NCLOB - для внутреннего символьного объекта, учитывающего национальный набор символов.

Для работы с 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.

bd0303.gif

Рис.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:

  • Содержит неструктурированные данные.

  • Поддерживает серверное фрагментирование.

  • Требует больше ресурсов на стороне клиента и сервера и больше загружает каналы связи, чем тип данных LOB.

  • При выполнении операций копирования для BFILE копируется только ссылка на файл. Данные этого файла не копируются.

Расположение каждого непустого объекта OracleBFile определяет:

  • Объект Oracle DIRECTORY - имя псевдонима полного пути к файлам данных;

  • Имя файла, расположенного в папке.

В следующем примере показано как можно отобразить рисунки, которые хранятся в директории "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.

bdbd0304.gif

Рис.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;

bdbd0305.gif

Рис.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;

bdbd0306.gif

Рис.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;

bdbd0307.gif

Рис.7 URI типы. Хранение в таблице БД

На Рис.8. показан файл, сформированный с использованием DBUriType и его отображение в Internet Explorer. Файлы html, копии странички http://wladm.narod.ru здесь не приводятся, однако следует отметить, что Encoding может быть другим (зависит от настроек Oracle).

bdbd0308.gif

Рис.8 Файл, сформированный с использованием DBUriType


В начало

Хранение XML типов в Oracle

Большинство баз данных обладают реляционной структурой, а документы XML - иерархической. По данной причине используется два подхода к хранению XML документов:

  • Хранение с использованием деконструирование XML-файла в реляционные данные (то есть хранение собственно данных, без тэгов).

  • Хранения в виде большого символьного объекта - Character Large Object (CLOBили BLOB). 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.

    bdbd0309.gif

    Рис.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";
    

    bdbd0310.gif

    Рис.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;
    

    bdbd0311.gif

    Рис.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";
    

    bdbd0312.gif

    Рис.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);
    

    bdbd0313.gif

    Рис.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);
    

    bdbd0314.gif

    Рис.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.

    bdbd0315.gif

    Рис.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).

    На главную страницу

    В начало страницы

    К началу раздела


  • Сайт управляется системой uCoz