Skip to content

5. Studium przypadku z wykorzystaniem Oracle Database Express Edition 11g Release 2

5.1. Instalacja narzędzi

Należy zainstalować następujące narzędzia:

  • SGBD: [http://www.oracle.com/technetwork/products/express-edition/downloads/index.html];
  • narzędzie administracyjne: EMS SQL Manager for Oracle Freeware [http://www.sqlmanager.net/fr/products/oracle/manager/download];
  • klient Oracle dla .NET: ODAC 11.2 Release 5 (11.2.0.3.20) z narzędziami Oracle Developer Tools dla Visual Studio: [http://www.oracle.com/technetwork/developer-tools/visual-studio/downloads/index.html].

W poniższych przykładach użytkownik „system” ma hasło „system”.

Uruchommy Oracle [1], a następnie narzędzie [SQL Manager Lite for Oracle], za pomocą którego będziemy zarządzać SGBD i [2].

  • w [3] łączymy się z istniejącą bazą danych;
  • w [4] korzystamy z usługi Oracle XE w celu nawiązania połączenia;
  • w [5] podajemy nazwę bazy danych XE;
  • w [6] logujemy się jako system / system;
  • w [7] zamykamy kreatora;
  • w [8] logujemy się do bazy danych;
  • w [9] jesteśmy zalogowani;
  • ponieważ zalogowaliśmy się jako użytkownik system / system, który ma rozszerzone uprawnienia, będziemy mogli na przykład zarządzać użytkownikami [10];
  • w [11] tworzymy nowego użytkownika;
  • o nazwie [12], będzie on nosił nazwę [RDVMEDECINS-EF];
  • w [13], a jego hasło to rdvmedecins;
  • w [14] zatwierdzamy utworzenie użytkownika;
  • w [15] użytkownik został utworzony;
  • w [16] użytkownik [RDVMEDECINS-EF] jest również schematem bazy danych;
  • w [17] utworzony użytkownik nie posiada wystarczających uprawnień. Przyznajemy mu je za pomocą skryptu SQL;
  • w [18] skrypt został uruchomiony;
  • w [19] spróbujemy zalogować się pod tożsamością [RDVMEDECINS-EF], aby sprawdzić, jakie ma uprawnienia. W tym celu najpierw rejestrujemy nową bazę w [EMS Manager];
  • w [19] logujemy się za pośrednictwem usługi XE;
  • w [20] logujemy się pod identyfikatorem RDVMEDECINS-EF / rdvmedecins;
  • w [21] podaje się alias odzwierciedlający nazwę zalogowanego użytkownika;
  • w [22] logujemy się do Oracle przy użyciu podanych danych;
  • w schemacie [22] udało się nawiązać połączenie;
  • w [23] próbujemy utworzyć tabelę w schemacie [RDVMEDECINS-EF];
  • w [24] definiujemy dowolną tabelę;
  • w [25] zatwierdzamy jej definicję;
  • w [26] tabela została utworzona. Usuwamy ją;
  • w [27] została usunięta.

Teraz, gdy mamy użytkownika z odpowiednimi uprawnieniami, utworzymy projekt VS 2012, który utworzy tabele schematu [RDVMEDECINS-EF] na podstawie definicji encji.

5.2. Tworzenie bazy danych na podstawie encji

Zaczynamy od skopiowania folderu projektu [RdvMedecins-SqlServer-01] do [RdvMedecins-Oracle-01] i [1]:

  • do [2], w VS 2012 usuwamy projekt [RdvMedecins-SqlServer-01] z rozwiązania;
  • w [3] projekt został usunięty;
  • w [4] dodajemy kolejny projekt. Znajduje się on w folderze [RdvMedecins-Oracle-01], który utworzyliśmy wcześniej;
  • w pliku [5] załadowany projekt nosi nazwę [RdvMedecins-SqlServer-01];
  • w [6] zmieniamy jego nazwę na [RdvMedecins-Oracle-01]
  • w [7] dodajemy kolejny projekt do rozwiązania. Znajduje się on w folderze [RdvMedecins-SqlServer-01] projektu, który wcześniej usunęliśmy z rozwiązania;
  • w pliku [8] projekt [RdvMedecins-SqlServer-01] został ponownie dodany do rozwiązania.

Projekt [RdvMedecins-Oracle-01] jest identyczny z projektem [RdvMedecins-SqlServer-01]. Musimy wprowadzić kilka zmian. W projekcie [App.config] zmodyfikujemy ciąg połączenia, a projekt [DbProviderFactory] należy dostosować do każdego projektu SGBD.


<!-- ciąg połączenia z bazą danych -->
  <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>
  <!-- dostawca fabryczny -->
  <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>
  • wiersz 3: nazwa użytkownika i hasło;
  • wiersze 6–11: plik DbProviderFactory. Wiersz 9 odwołuje się do pliku DLL i [Oracle.DataAccess], których nie posiadamy. Można go uzyskać za pomocą plików NuGet i [1]:
  • w [2], w polu wyszukiwania wpisujemy słowo kluczowe oracle;
  • w przypadku [3] należy wybrać odpowiedni pakiet [Oracle Data Provider]. Jest to łącznik ADO.NET firmy Oracle;
  • w [4] dodano numer referencyjny;
  • w [5], w [App.config] należy wpisać prawidłową wersję z DLL. Można ją znaleźć w jego właściwościach.

W pliku [Entites.cs] należy dostosować schemat tabel, które zostaną wygenerowane. Używany schemat to nazwa użytkownika będącego właścicielem tabel.


  [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
  {...}

Konfigurujemy realizację projektu:

  • w [1] nadajemy inną nazwę generowanemu zestawowi;
  • w [2] określamy również inną domyślną przestrzeń nazw;
  • w [3] wskazujemy program do uruchomienia.

Na tym etapie nie ma żadnych błędów kompilacji. Uruchommy program [CreateDB_01]. Otrzymujemy następujący wyjątek:

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

Pamiętamy, że ten sam błąd wystąpił w przypadku programu MySQL. Jest to związane z typem pola Timestamp w encjach. Wprowadzamy tę samą zmianę. W encjach zastępujemy trzy wiersze


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

następującymi:


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

Zmieniamy zatem typ kolumny z byte[] na int?. Przypomnijmy, że zarówno w przypadku SQL Server, jak i MySQL, kolumna w tabelach służąca do zarządzania współbieżnością dostępu otrzymywała wartość z SGBD za każdym razem, gdy wstawiano lub modyfikowano wiersz. Od tej pory będziemy używać pola encji, które będzie liczbą całkowitą. W SGBD będziemy korzystać z procedur przechowywanych, aby zwiększać tę liczbę całkowitą o jeden za każdym razem, gdy wstawiany lub modyfikowany jest wiersz.

Wprowadzamy powyższą zmianę dla wszystkich czterech encji, a następnie ponownie uruchamiamy aplikację. Otrzymujemy wówczas następujący błąd:

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

Wiersz 1 wskazuje, że łącznik Oracle o nazwie ADO.NET nie jest w stanie usunąć istniejącej bazy danych. Przypomnijmy, co się dzieje. Kod [CreateDB_01.cs] wygląda następująco:


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

namespace RdvMedecins_01
{
  class CreateDB_01
  {
    static void Main(string[] args)
    {
      // tworzymy bazę danych
      Database.SetInitializer(new RdvMedecinsInitializer());
      using (var context = new RdvMedecinsContext())
      {
        context.Database.Initialize(false);
      }
    }
  }
}

Wiersz 15 uruchamia wykonanie klasy [RdvMedecinsInitializer] (wiersz 12). Wygląda ona następująco:


  public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>

Wywodzi się ona z klasy [DropCreateDatabaseAlways], która próbuje usunąć, a następnie odtworzyć bazę danych. Zmieniamy definicję klasy na:


  public class RdvMedecinsInitializer : CreateDatabaseIfNotExists<RdvMedecinsContext>

Baza danych jest tworzona tylko wtedy, gdy nie istnieje. Ponownie uruchamiamy [CreateDB_01.cs] i tym razem nie ma już błędów. Jednak w [EMS Manager] zauważamy, że baza danych [RDVMEDECINS-EF] pozostała pusta. Ponieważ program EF 5 znalazł istniejącą bazę, nie wykonał żadnych czynności. Działa on tylko wtedy, gdy baza nie istnieje. Od tego momentu sytuacja staje się błędnym kołem. W rzeczywistości ciąg połączenia dla programu SGBD wygląda następująco:


  <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>

W wierszu 2 ciąg połączenia nie zawiera nazwy bazy danych, lecz nazwę użytkownika. Użytkownik ten musi istnieć.

Jesteśmy zatem zmuszeni do ręcznego utworzenia bazy danych [RDVMEDECINS-EF] za pomocą narzędzia [EMS Manager for Oracle]. Nie opisujemy wszystkich etapów, a jedynie te najważniejsze.

Baza danych Oracle będzie miała następujący wygląd:

Tabele

Poszczególne tabele posiadają klucze główne i obce, które te same tabele miały w dwóch poprzednich przykładach. Klucze obce mają w szczególności atrybuty ON, DELETE oraz CASCADE.

Sekwencje

Utworzono tutaj sekwencje Oracle. Są to generatory kolejnych liczb. Jest ich 5: [1].

  • W sekwencji [2] widzimy właściwości sekwencji [SEQUENCE_CLIENTS]. Generuje ona kolejne liczby o różnicy 1, począwszy od 1 aż do bardzo dużej wartości.

Wszystkie sekwencje są zbudowane według tego samego wzorca.

  • Sekwencja [SEQUENCE_CLIENTS] zostanie wykorzystana do wygenerowania klucza głównego tabeli [CLIENTS];
  • [SEQUENCE_MEDECINS] zostanie wykorzystana do wygenerowania klucza głównego tabeli [MEDECINS];
  • [SEQUENCE_CRENEAUX] zostanie wykorzystany do wygenerowania klucza głównego tabeli [CRENEAUX];
  • [SEQUENCE_RVS] zostanie wykorzystany do wygenerowania klucza głównego tabeli [RVS];
  • [SEQUENCE_VERSIONS] zostanie wykorzystana do wygenerowania wartości kolumn [VERSIONING] we wszystkich tabelach.

Wyzwalacze

Wyzwalacz to procedura wykonywana przez SGBD przed lub po zdarzeniu (wstawienie, modyfikacja, usunięcie) w tabeli. Mamy ich 8 [1]:

 

Przyjrzyjmy się kodowi DDL wyzwalacza [TRIGGER_PK_CLIENTS], który zasila klucz główny tabeli [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;
/
  • wiersze 1–5: przed każdą operacją INSERT na tabeli [CLIENTS];
  • wiersz 6: kolumna [ID] przyjmie następną wartość z sekwencji [SEQUENCE_CLIENTS]. Klucz główny będzie w ten sposób posiadał kolejne wartości dostarczane przez sekwencję.

Wyzwalacze [TRIGGER_PK_MEDECINS, TRIGGER_PK_CRENEAUX, TRIGGER_PK_RVS] działają analogicznie.

Przyjrzyjmy się kodowi DDL wyzwalacza [TRIGGER_VERSIONS_CLIENTS], który zasila kolumnę [VERSIONING] w tabeli [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;
/
  • wiersze 1–2: przed każdą operacją INSERT lub UPDATE na tabeli [CLIENTS];
  • wiersz 8: kolumna [VERSIONING] przyjmie następną wartość z sekwencji [SEQUENCE_VERSIONS]. Kolumna [VERSIONING] będzie w ten sposób zawierała kolejne wartości dostarczane przez sekwencję.

Wyzwalacze [TRIGGER_VERSION_MEDECINS, TRIGGER_VERSION_CRENEAUX, TRIGGER_VERSION_RVS] działają analogicznie. Cztery kolumny [VERSIONING] pobierają swoje wartości z tej samej sekwencji.

Skrypt generujący tabele bazy danych Oracle o nazwie [RDVMEDECINS-EF] został umieszczony w folderze [RdvMedecins / databases / oracle]. Czytelnik może go załadować i uruchomić w celu utworzenia tabel.

Po wykonaniu tej czynności można uruchomić poszczególne programy projektu. Dają one takie same wyniki jak w przypadku serwera SQL, z wyjątkiem programu [ModifyDetachedEntities], który ulega awarii z tego samego powodu, co w przypadku MySQL. Problem rozwiązuje się w ten sam sposób. Wystarczy skopiować program [ModifyDetachedEntities] z projektu [RdvMedecins-MySQL-01] do projektu [RdvMedecins-Oracle-01]. Pojawia się wówczas nowy problem:

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: Instrukcja aktualizacji, wstawienia lub usunięcia w magazynie dotyczyła nieoczekiwanej liczby wierszy (0). Być może od momentu załadowania niektóre encje zostały zmodyfikowane lub usunięte. Zaktualizuj wpisy 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
  • wiersze 1–4: odłączony klient został poprawnie zaktualizowany;
  • wiersz 6: znany wyjątek. Występuje on, gdy próbuje się zmodyfikować encję, nie mając odpowiedniej wersji. Jednak w tym przypadku nie chcieliśmy modyfikować, lecz usunąć encję:

      // usunięcie encji poza kontekstem
      using (var context = new RdvMedecinsContext())
      {
        // tutaj mamy nowy pusty kontekst
        // umieszczamy klienta1 w kontekście w stanie usuniętym
        context.Entry(client1).State = EntityState.Deleted;
        // zapisujemy kontekst
        context.SaveChanges();
}

EF 5 odmówił usunięcia client1 z bazy danych, ponieważ client1 (wiersz 6) nie miał tej samej wersji. Nie napotkaliśmy tego problemu w przypadku MySQL. Stopniowo zauważamy, że łączniki ADO.NET z różnych SGBD wykazują niewielkie różnice. Poprawiamy to w następujący sposób:


using (var context = new RdvMedecinsContext())
      {
        // tutaj mamy nowy pusty kontekst
        // umieszczamy „client1” w kontekście w celu jego usunięcia
        context.Clients.Remove(context.Clients.Find(client1.Id));
        // zapisujemy kontekst
        context.SaveChanges();
      }

i to działa.

5.3. Architektura wielowarstwowa oparta na EF 5

Wracamy do naszego studium przypadku opisanego w akapicie 2 na stronie 7.

Zaczniemy od zbudowania warstwy dostępu do danych [DAO]. W tym celu powielamy projekt konsoli VS 2012 [RdvMedecins-SqlServer-02] w [RdvMedecins-Oracle-02] [1]:

  • do [2], usuwamy projekt [RdvMedecins-SqlServer-02];
  • w [3] dodajemy istniejący projekt do rozwiązania. Pobieramy go z właśnie utworzonego folderu [RdvMedecins-Oracle-02];
  • w [4] nowy projekt nosi nazwę tego, który został usunięty. Zmienimy jego nazwę;
  • w [5] zmieniono nazwę projektu;
  • w [6] zmieniono niektóre właściwości, np. nazwę zestawu;
  • na [7], folder [Models] został usunięty i zastąpiony folderem [Models] z projektu [RdvMedecins-Oracle-01]. Oba projekty korzystają bowiem z tych samych szablonów.
  • w [8] – aktualne odniesienia projektu;
  • w [9] dodano łącznik Oracle ADO.NET za pomocą narzędzia NuGet.

W pliku [App.config] zastępuje się dane z bazy SQL Server danymi z bazy Oracle. Znajdują się one w pliku [App.config] projektu [RdvMedecins-Oracle-01]:


<!-- łańcuch połączenia z bazą danych -->
  <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>
  <!-- dostawca fabryki -->
  <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>

Zmieniają się również obiekty zarządzane przez Spring. Obecnie mamy:


  <!-- konfiguracja 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>

Wiersz 7 odwołuje się do zestawu projektu [RdvMedecins-SqlServer-02]. Zestaw ten nosi teraz nazwę [RdvMedecins-Oracle-02].

Po wykonaniu tej czynności jesteśmy gotowi do przeprowadzenia testu warstwy [DAO]. Wcześniej należy zadbać o wypełnienie bazy danych (program [Fill] z projektu [RdvMedecins-Oracle-01]). Test programu zakończył się powodzeniem.

Tworzymy plik DLL projektu w taki sam sposób, jak w przypadku projektu [RdvMedecins-SqlServer-02], a następnie gromadzimywszystkie pliki DLL z projektu w folderze [lib] utworzonym w [RdvMedecins-Oracle-02]. Będą to pliki referencyjne dla projektu internetowego [RdvMedecins-Oracle-03], który zostanie zrealizowany w następnej kolejności.

  

Jesteśmy teraz gotowi do stworzenia warstwy [ASP.NET] naszej aplikacji:

Wychodzimy od projektu [RdvMedecins-SqlServer-03]. Duplikujemy folder tego projektu w [RdvMedecins-Oracle-03] i [1]:

  • do [2], przy użyciu programu VS 2012 Express for the Web otwieramy rozwiązanie z folderu [RdvMedecins-Oracle-03];
  • w pliku [3] zmieniamy zarówno nazwę rozwiązania, jak i nazwę projektu;
  • w pliku [4] aktualne odniesienia projektu;
  • w pliku [5] usuwamy je;
  • na [6], aby zastąpić je odwołaniami do pliku DLL, który właśnie zapisaliśmy w folderze [lib] projektu [RdvMedecins-Oracle-02].

Pozostaje nam tylko zmodyfikować plik [Web.config]. Zastępujemy jego aktualną zawartość zawartością pliku [App.config] z projektu [RdvMedecins-Oracle-02]. Po wykonaniu tej czynności uruchamiamy projekt internetowy. Działa. Nie zapomnijmy o zapełnieniu bazy danych przed uruchomieniem aplikacji internetowej.