Skip to content

5. Приклад використання Oracle Database Express Edition 11g Release 2

5.1. Встановлення інструментів

Необхідно встановити такі інструменти:

  • SGBD: [http://www.oracle.com/technetwork/products/express-edition/downloads/index.html];
  • інструмент адміністрування: EMS SQL Manager for Oracle Freeware [http://www.sqlmanager.net/fr/products/oracle/manager/download];
  • клієнт Oracle для .NET: ODAC 11.2 Release 5 (11.2.0.3.20) з Oracle Developer Tools для Visual Studio: [http://www.oracle.com/technetwork/developer-tools/visual-studio/downloads/index.html].

У наведених нижче прикладах користувач system має пароль system.

Запустимо Oracle [1], а потім інструмент [SQL Manager Lite for Oracle], за допомогою якого ми будемо адмініструвати SGBD та [2].

  • у [3] ми підключаємося до існуючої бази даних;
  • у [4] ми використовуємо службу Oracle XE для підключення;
  • у [5] вказуємо ім’я бази даних XE;
  • у [6] підключаємося як system / system;
  • у [7] завершуємо роботу майстра;
  • у [8] — підключаємося до бази даних;
  • у [9] — підключено;
  • оскільки ми підключилися як користувач system / system, який має розширені права, ми зможемо, наприклад, керувати користувачами [10];
  • у [11] ми створюємо нового користувача;
  • [12], його ім'я буде [RDVMEDECINS-EF];
  • у [13], і матиме пароль rdvmedecins;
  • у [14] підтверджується створення користувача;
  • у [15] користувач створено;
  • у [16] користувач [RDVMEDECINS-EF] також є схемою бази даних;
  • у [17] користувач, як його було створено, не має достатніх прав. Ми надаємо йому їх за допомогою скрипта SQL;
  • у [18] виконується скрипт;
  • у [19] ми спробуємо увійти під обліковим записом [RDVMEDECINS-EF], щоб перевірити, що він може робити. Для цього спочатку зареєструємо нову базу даних у [EMS Manager];
  • у [19] ми входимо через службу XE;
  • у [20] входимо під ім’ям RDVMEDECINS-EF / rdvmedecins;
  • у [21] вказується псевдонім, що відображає ім’я користувача, який входить у систему;
  • у [22] — здійснюється вхід в Oracle з вказаними даними;
  • у [22] вдалося встановити з'єднання;
  • у [23] намагаємося створити таблицю у схемі [RDVMEDECINS-EF];
  • у [24] визначається довільна таблиця;
  • у [25] — підтверджується її визначення;
  • у [26] таблиця була створена. Її видаляємо;
  • у [27] — вона видалена.

Тепер, коли ми маємо користувача з достатніми правами, створимо проект VS 2012, який створить таблиці схеми [RDVMEDECINS-EF] на основі визначення сутностей.

5.2. Створення бази даних на основі сутностей

Почнемо з дублювання папки проєкту [RdvMedecins-SqlServer-01] у [RdvMedecins-Oracle-01] та [1]:

  • у [2], у VS 2012 видаляємо проект [RdvMedecins-SqlServer-01] із рішення;
  • у [3] проект було видалено;
  • у [4] додаємо ще один. Цей проект міститься в папці [RdvMedecins-Oracle-01], яку ми створили раніше;
  • у [5] завантажений проект має назву [RdvMedecins-SqlServer-01];
  • у [6] змінюємо його назву на [RdvMedecins-Oracle-01]
  • у [7] до розв’язання додають ще один проєкт. Він міститься у папці [RdvMedecins-SqlServer-01] того проєкту, який ми раніше видалили з розв’язання;
  • у [8] проект [RdvMedecins-SqlServer-01] знову включено до рішення.

Проєкт [RdvMedecins-Oracle-01] ідентичний проєкту [RdvMedecins-SqlServer-01]. Нам потрібно внести кілька змін. У [App.config] ми змінимо рядок підключення, а [DbProviderFactory] потрібно адаптувати до кожного SGBD.


<!-- рядок підключення до бази даних -->
  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
  </connectionStrings>
  <!-- фабричний провайдер -->
  <system.data>
    <DbProviderFactories>
      <remove invariant="Oracle.DataAccess.Client" />
      <add name="Oracle Data Provider for .NET" invariant="Oracle.DataAccess.Client" description="Oracle Data Provider for .NET" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
    </DbProviderFactories>
  </system.data>
  • рядок 3: ім’я користувача та його пароль;
  • рядки 6–11: файл DbProviderFactory. У рядку 9 згадується файл DLL [Oracle.DataAccess], якого у нас немає. Його можна отримати за допомогою NuGet та [1]:
  • у [2] у полі пошуку вводимо ключове слово oracle;
  • у [3] виберіть відповідний пакет [Oracle Data Provider]. Це коннектор ADO.NET від Oracle;
  • у [4] додано посилання;
  • у [5], у [App.config] потрібно вказати правильну версію DLL. Її можна знайти у властивостях.

У файлі [Entites.cs] потрібно адаптувати схему таблиць, які будуть згенеровані. Схема, що використовується, — це ім’я користувача-власника таблиць.


  [Table("MEDECINS", Schema = "RDVMEDECINS-EF")]
  public class Medecin : Personne
  {...}

  [Table("CLIENTS", Schema = "RDVMEDECINS-EF")]
  public class Client : Personne
  {...}

  [Table("RVS", Schema = "RDVMEDECINS-EF")]
  public class Rv
  {...}

  [Table("CRENEAUX", Schema = "RDVMEDECINS-EF")]
  public class Creneau
  {...}

Налаштовуємо виконання проекту:

  • у [1] ми присвоюємо іншу назву збірці, яка буде згенерована;
  • у [2] також задаємо інший простір імен за замовчуванням;
  • у [3] вказуємо програму, яку потрібно виконати.

На цьому етапі помилок компіляції немає. Запустимо програму [CreateDB_01]. Отримуємо таке виключення:

Exception non gérée : System.Data.MetadataException: Le schéma spécifié n'est pas valide. Erreurs :
(11,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(23,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
   à System.Data.Metadata.Edm.StoreItemCollection.Loader.ThrowOnNonWarningErrors
()
   ...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

Пригадуємо, що таку саму помилку ми отримували з MySQL. Це пов’язано з типом поля Timestamp в сутностях. Вносимо ту саму зміну. У сутностях замінюємо три рядки


    [Column("TIMESTAMP")]
    [Timestamp]
    public byte[] Timestamp { get; set; }

на такі:


    [ConcurrencyCheck]
    [Column("VERSIONING")]
    public int? Versioning { get; set; }

Отже, змінюємо тип стовпця з byte[] на int?. Нагадаємо, що як для SQL Server, так і для MySQL стовпець таблиць, який використовувався для управління конкуренцією доступу, отримував значення з SGBD щоразу, коли вставлявся або змінювався рядок. Відтепер ми будемо використовувати поле сутності, яке буде цілим числом. У SGBD ми будемо використовувати збережені процедури для збільшення цього цілого числа на одиницю щоразу, коли буде вставлено або змінено рядок.

Ми вносимо зазначену зміну для всіх чотирьох сутностей, а потім запускаємо програму знову. Після цього отримуємо таку помилку:

1
2
3
4
5
6
Exception non gérée : System.Data.DataException: An exception occurred while initializing the database. See the InnerException for details. ---> System.Data.ProviderIncompatibleException: DeleteDatabase n'est pas pris en charge par le fournisseur.
   à System.Data.Common.DbProviderServices.DbDeleteDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   ...
   à System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

У рядку 1 вказано, що коннектор Oracle ADO.NET не може видалити існуючу базу даних. Нагадаємо, що відбувається. Код [CreateDB_01.cs] виглядає так:


using System;
using System.Data.Entity;
using RdvMedecins.Models;

namespace RdvMedecins_01
{
  class CreateDB_01
  {
    static void Main(string[] args)
    {
      // створюється база даних
      Database.SetInitializer(new RdvMedecinsInitializer());
      using (var context = new RdvMedecinsContext())
      {
        context.Database.Initialize(false);
      }
    }
  }
}

У рядку 15 запускається виконання класу [RdvMedecinsInitializer] (рядок 12). Він має такий вигляд:


  public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>

Вона походить від класу [DropCreateDatabaseAlways], який намагається видалити, а потім відтворити базу даних. Ми змінюємо визначення класу на:


  public class RdvMedecinsInitializer : CreateDatabaseIfNotExists<RdvMedecinsContext>

База створюється лише в тому випадку, якщо її ще немає. Ми знову запускаємо [CreateDB_01.cs], і тепер помилок більше немає. Але в [EMS Manager] ми бачимо, що база [RDVMEDECINS-EF] залишилася порожньою. Оскільки EF 5 виявив існуючу базу даних, він нічого не зробив. Він виконує дії лише в тому випадку, якщо база даних не існує. З цього моменту ми потрапляємо в замкнуте коло. Дійсно, рядок підключення до SGBD має такий вигляд:


  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
</connectionStrings>

У рядку 2 рядок підключення використовує не ім’я бази даних, а ім’я користувача. Цей користувач має існувати.

Тому нам доводиться вручну створити базу даних [RDVMEDECINS-EF] за допомогою інструменту [EMS Manager for Oracle]. Ми не описуємо всі етапи, а лише найважливіші.

База даних Oracle матиме такий вигляд:

Таблиці

Різні таблиці мають первинні та зовнішні ключі, які були у цих самих таблицях у двох попередніх прикладах. Зокрема, зовнішні ключі мають атрибути ON, DELETE та CASCADE.

Послідовності

Тут створено послідовності Oracle. Це генератори послідовних чисел. Їх є 5: [1].

  • у [2] ми бачимо властивості послідовності [SEQUENCE_CLIENTS]. Вона генерує послідовні числа з кроком 1, починаючи з 1 і аж до дуже великого значення.

Усі послідовності побудовані за одним і тим самим зразком.

  • [SEQUENCE_CLIENTS] буде використовуватися для генерації первинного ключа таблиці [CLIENTS];
  • [SEQUENCE_MEDECINS] буде використовуватися для генерації первинного ключа таблиці [MEDECINS];
  • [SEQUENCE_CRENEAUX] буде використовуватися для генерації первинного ключа таблиці [CRENEAUX];
  • [SEQUENCE_RVS] буде використано для генерації первинного ключа таблиці [RVS];
  • [SEQUENCE_VERSIONS] буде використовуватися для генерації значень стовпців [VERSIONING] у всіх таблицях.

Тригери

Тригер — це процедура, що виконується SGBD до або після події (вставлення, зміна, видалення) у таблиці. У нас їх 8 [1]:

 

Розглянемо код DDL тригера [TRIGGER_PK_CLIENTS], який заповнює первинний ключ таблиці [CLIENTS]:

1
2
3
4
5
6
7
8
CREATE TRIGGER "RDVMEDECINS-EF".TRIGGER_PK_CLIENTS
 BEFORE INSERT
 ON "RDVMEDECINS-EF".CLIENTS
REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW
BEGIN
        SELECT SEQUENCE_CLIENTS.NEXTVAL INTO :new.ID from DUAL;
END;
/
  • рядки 1–5: перед кожною операцією INSERT над таблицею [CLIENTS];
  • рядок 6: стовпець [ID] прийме наступне значення з послідовності [SEQUENCE_CLIENTS]. Таким чином, первинний ключ матиме послідовні значення, що надаються послідовністю.

Тригери [TRIGGER_PK_MEDECINS, TRIGGER_PK_CRENEAUX, TRIGGER_PK_RVS] працюють аналогічно.

Розглянемо код DDL тригера [TRIGGER_VERSIONS_CLIENTS], який заповнює стовпець [VERSIONING] таблиці [CLIENTS]:

CREATE TRIGGER "RDVMEDECINS-EF".TRIGGER_VERSION_CLIENTS
 BEFORE INSERT OR UPDATE
 OF
  VERSIONING
 ON "RDVMEDECINS-EF".CLIENTS
REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW
BEGIN
        SELECT SEQUENCE_VERSIONS.NEXTVAL INTO :new.VERSIONING from DUAL;
END;
/
  • рядки 1–2: перед кожною операцією INSERT або UPDATE над таблицею [CLIENTS];
  • рядок 8: стовпець [VERSIONING] прийме наступне значення з послідовності [SEQUENCE_VERSIONS]. Таким чином, стовпець [VERSIONING] матиме послідовні значення, надані послідовністю.

Тригери [TRIGGER_VERSION_MEDECINS, TRIGGER_VERSION_CRENEAUX, TRIGGER_VERSION_RVS] працюють аналогічно. Чотири стовпці [VERSIONING] отримують свої значення з тієї самої послідовності.

Скрипт для створення таблиць бази даних Oracle [RDVMEDECINS-EF] розміщено у папці [RdvMedecins / databases / oracle]. Читач може завантажити та виконати його для створення своїх таблиць.

Після цього можна запускати різні програми проекту. Вони дають ті самі результати, що й із сервером SQL, за винятком програми [ModifyDetachedEntities], яка вилітає з тієї самої причини, що й із MySQL. Проблему вирішують таким самим чином. Достатньо скопіювати програму [ModifyDetachedEntities] з проєкту [RdvMedecins-MySQL-01] у проєкт [RdvMedecins-Oracle-01]. Тоді виникає нова проблема:

1
2
3
4
5
6
7
8
9
1-----------------------------
Client [206,x,x,x,616]
2-----------------------------
Client [206,x,x,y,617]

Exception non gérée : System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Une instruction de mise à jour, d'insertion ou de suppression dans le magasin a affecté un nombre inattendu de lignes (0). Des entités ont peut-être é modifiées ou supprimées depuis leur chargement. Actualisez les entrées ObjectStateManager. ---> System.Data.OptimisticConcurrencyException: Команда оновлення, вставки або видалення в сховищі вплинула на несподівану кількість рядків (0). Можливо, сутності були змінені або видалені після їх завантаження. Оновіть записи ObjectStateManager.
   ...
   à RdvMedecins_01.ModifyDetachedEntities.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\ModifyDetachedE
ntities.cs:ligne 56
  • рядки 1–4: відокремлений клієнт було успішно оновлено;
  • рядок 6: відоме виключення. Воно виникає, коли намагаються змінити об’єкт, не маючи правильної версії. Однак у цьому випадку ми хотіли не змінити, а видалити об’єкт:

      // видалення сутності поза контекстом
      using (var context = new RdvMedecinsContext())
      {
        // тут ми маємо новий порожній контекст
        // вносимо «client1» у контекст у стані «видалено»
        context.Entry(client1).State = EntityState.Deleted;
        // зберігаємо контекст
        context.SaveChanges();
}

EF 5 відмовився видалити client1 з бази даних, оскільки client1 (рядок 6) мав іншу версію. З MySQL такої проблеми не виникало. Поступово ми помічаємо, що з’єднувачі ADO.NET різних SGBD мають незначні відмінності. Вносимо виправлення таким чином:


using (var context = new RdvMedecinsContext())
      {
        // тут ми маємо новий порожній контекст
        // додаємо «client1» до контексту, щоб видалити його
        context.Clients.Remove(context.Clients.Find(client1.Id));
        // зберігаємо контекст
        context.SaveChanges();
      }

і все працює.

5.3. Багаторівнева архітектура на основі EF 5

Повернемося до нашого прикладу, описаного в параграфі 2 на сторінці 7.

Почнемо з побудови шару [DAO] для доступу до даних. Для цього ми дублюємо проект консолі VS 2012 [RdvMedecins-SqlServer-02] у [RdvMedecins-Oracle-02] [1]:

  • у [2] видаляємо проект [RdvMedecins-SqlServer-02];
  • у [3] додаємо існуючий проект до рішення. Беремо його з папки [RdvMedecins-Oracle-02], яку щойно створили;
  • у [4] новий проєкт має назву того, що було видалено. Ми змінимо його назву;
  • у [5] ми змінили назву проєкту;
  • у [6] ми змінюємо деякі його властивості, наприклад, назву збірки;
  • на [7], папку [Models] видалено, щоб замінити її папкою [Models] з проєкту [RdvMedecins-Oracle-01]. Адже обидва проєкти використовують однакові шаблони.
  • у [8] — поточні посилання проекту;
  • у [9] було додано коннектор ADO.NET від Oracle за допомогою інструменту NuGet.

У файлі [App.config] дані з бази SQL Server замінюються даними з бази Oracle. Їх можна знайти у файлі [App.config] проекту [RdvMedecins-Oracle-01]:


<!-- ланцюг з'єднання з базою даних -->
  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
  </connectionStrings>
  <!-- фабрика провайдерів -->
  <system.data>
    <DbProviderFactories>
      <remove invariant="Oracle.DataAccess.Client" />
      <add name="Oracle Data Provider for .NET" invariant="Oracle.DataAccess.Client" description="Oracle Data Provider for .NET" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
    </DbProviderFactories>
  </system.data>

Об’єкти, що керуються Spring, також змінюються. Наразі маємо:


  <!-- конфігурація Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
    </objects>
</spring>

У рядку 7 вказано збірку проекту [RdvMedecins-SqlServer-02]. Тепер збірка має назву [RdvMedecins-Oracle-02].

Тепер ми готові виконати тест шару [DAO]. Перед цим потрібно подбати про заповнення бази даних (програма [Fill] з проєкту [RdvMedecins-Oracle-01]). Тестова програма проходить успішно.

Ми створюємо DLL для цього проєкту так само, як це було зроблено для проєкту [RdvMedecins-SqlServer-02], і збираємоусі файли DLL цього проєкту в папку [lib], створену в [RdvMedecins-Oracle-02]. Це будуть посилання для веб-проєкту [RdvMedecins-Oracle-03], який буде створено далі.

  

Тепер ми готові до створення шару [ASP.NET] нашого додатка:

Ми почнемо з проєкту [RdvMedecins-SqlServer-03]. Скопіюємо папку цього проєкту в [RdvMedecins-Oracle-03] та [1]:

  • у [2], за допомогою VS 2012 Express для веб-розробки відкриваємо рішення з папки [RdvMedecins-Oracle-03];
  • у [3] ми змінюємо як назву рішення, так і назву проєкту;
  • у [4] — поточні посилання проекту;
  • у [5] видаляємо їх;
  • на [6], щоб замінити їх посиланнями на DLL, які ми щойно зберегли у папці [lib] проекту [RdvMedecins-Oracle-02].

Залишилося лише змінити файл [Web.config]. Замінюємо його поточний вміст на вміст файлу [App.config] з проєкту [RdvMedecins-Oracle-02]. Зробивши це, запускаємо веб-проєкт. Він працює. Не забуваємо заповнити базу даних перед запуском веб-додатку.