Skip to content

6. Studium przypadku z wykorzystaniem pliku PostgreSQL 9.2.1

6.1. Instalacja narzędzi

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

  • SGBD: [http://www.enterprisedb.com/products-services-training/pgdownload#windows];
  • narzędzie administracyjne: EMS, SQL Manager for PostgreSQL, Freeware [http://www.sqlmanager.net/fr/products/postgresql/manager/download].

W poniższych przykładach użytkownik postgres ma hasło postgres.

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

  • w [1] uruchamiamy SGBD i PostgreSQL z poziomu usług systemu Windows;
  • w przypadku [2] usługa zostaje uruchomiona;

Teraz uruchamiamy narzędzie [SQL Manager Lite for MySQL], za pomocą którego będziemy zarządzać SGBD i [3].

  • w [4] tworzymy nową bazę;
  • w [5] podajemy nazwę bazy;
  • w pliku [5] logujemy się jako postgres / postgres;
  • w [6] podajemy kilka informacji;
  • w [7] zatwierdza się zlecenie SQL, które zostanie wykonane;
  • w [8] baza została utworzona. Teraz należy ją zarejestrować w [EMS Manager]. Informacje są poprawne. Wykonujemy [OK];
  • w [9] logujemy się do niej;
  • w [10], [EMS Manager] wyświetla bazę danych, która na razie jest pusta. Należy zauważyć, że tabele będą należeć do schematu o nazwie public [11].

Teraz podłączymy do tej bazy projekt VS 2012.

6.2. Tworzenie bazy danych na podstawie encji

Zaczynamy od skopiowania folderu projektu [RdvMedecins-SqlServer-01] do [RdvMedecins-PostgreSQL-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-PostgreSQL-01], który utworzyliśmy wcześniej;
  • w pliku [5] załadowany projekt nosi nazwę [RdvMedecins-SqlServer-01];
  • w [6] zmieniamy nazwę na [RdvMedecins-PostgreSQL-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 [8] projekt [RdvMedecins-SqlServer-01] został ponownie dodany do rozwiązania.

Projekt [RdvMedecins-PostgreSQL-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="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- dostawca fabryki -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </DbProviderFactories>
  </system.data>
  • wiersz 3: nazwa użytkownika i hasło;
  • wiersze 7–9: plik DbProviderFactory. Wiersz 8 odwołuje się do pliku DLL [Npgsql], którego nie posiadamy. Można go uzyskać za pomocą NuGet [1]:
  • w [2], w polu wyszukiwania wpisujemy słowo kluczowe postgresql;
  • w przypadku [3] należy wybrać pakiet [Npgsql]. Jest to łącznik ADO.NET dla PostgreSQL;
  • w [4] dodano dwa numery referencyjne;
  • w [5], w [App.config] należy podać prawidłową wersję DLL. Można ją znaleźć w jego właściwościach.

W pliku [Entites.cs] należy dostosować schemat tabel, które zostaną wygenerowane:


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

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

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

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

Jak widzieliśmy wcześniej podczas tworzenia bazy danych PostgreSQL, tabele należały do schematu o nazwie „public”.

Konfigurujemy uruchomienie projektu:

  • w [1] nadajemy inną nazwę generowanemu zestawowi;
  • w [2], a także 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 i Oracle. Jest to związane z typem pola Timestamp w encjach. Wprowadzamy tę samą modyfikację, co w przypadku Oracle. 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 typ kolumny z byte[] na int?. W SGBD wykorzystamy procedury przechowywane, 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 ADO.NET z PostgreSQL nie jest w stanie usunąć istniejącej bazy danych. Dokładnie tak samo jak w przypadku Oracle. Musimy zatem ręcznie utworzyć bazę danych [RDVMEDECINS-EF] za pomocą narzędzia [EMS Manager for PostgreSQL]. Nie opisujemy wszystkich kroków, a jedynie te najważniejsze.

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

Tabele

  • w [1], ID jest kluczem głównym typu serial. Ten typ PostgreSQL jest liczbą całkowitą generowaną automatycznie przez SGBD.

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

Sekwencje

Podobnie jak w przypadku Oracle, utworzyliśmy tutaj sekwencje. Są to generatory kolejnych liczb. Jest ich 5: [1].

  • W [2] widzimy właściwości sekwencji [CLIENTS_ID_SEQ]. 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 [CLIENTS_ID_seq] zostanie wykorzystana do wygenerowania klucza głównego tabeli [CLIENTS];
  • [MEDECINS_ID_seq] zostanie wykorzystana do wygenerowania klucza głównego tabeli [MEDECINS];
  • [CRENEAUX_ID_seq] zostanie wykorzystany do wygenerowania klucza głównego tabeli [CRENEAUX];
  • [RVS_ID_seq] 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 4 wyzwalacze [1]:

Przyjrzyjmy się kodowi DDL wyzwalacza [CLIENTS_tr], który zasilają kolumnę [VERSIONING] w tabeli [CLIENTS]:

1
2
3
4
CREATE TRIGGER "CLIENTS_tr"
  BEFORE INSERT OR UPDATE 
  ON public."CLIENTS" FOR EACH ROW 
EXECUTE PROCEDURE public.trigger_versions();
  • wiersze 1–3: przed każdą operacją INSERT lub UPDATE na tabeli [CLIENTS];
  • wiersz 4: wykonywana jest procedura [public.trigger_versions()].

Procedura [public.trigger_versions()] wygląda następująco:

1
2
3
4
BEGIN
NEW."VERSIONING":=nextval('sequence_versions');
return NEW;
END
  • wiersz 2: NEW oznacza wiersz, który ma zostać wstawiony lub zmodyfikowany. NEW. „VERSIONING” to kolumna [VERSIONING] tego wiersza. Przypisuje się jej następującą wartość z generatora liczb: „sequence_versions”. W ten sposób kolumna ["VERSIONING"] zmienia się przy każdym operacji INSERT / UPDATE wykonanej na tabeli [CLIENTS].

Podobnie działają wyzwalacze [MEDECINS_tr, CRENEAUX_tr, RVS_tr]. Cztery kolumny ["VERSIONING"] pobierają swoje wartości z tej samej sekwencji.

Skrypt generujący tabele bazy danych PostgreSQL i [RDVMEDECINS-EF] został umieszczony w folderze [RdvMedecins / databases / postgreSQL]. Użytkownik 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 Oracle. Problem ten rozwiązuje się w ten sam sposób. Wystarczy skopiować program [ModifyDetachedEntities] z projektu [RdvMedecins-Oracle-01] do projektu [RdvMedecins-PostgreSQL-01].

Program [LazyEagerLoading] ulega awarii z następującym wyjątkiem:

1
2
3
4
Exception non gérée : System.Data.EntityCommandExecutionException: Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne. ---> Npgsql.NpgsqlException: ERREUR: 42601: erreur de syntaxe sur ou près de « LEFT »
   à Npgsql.NpgsqlState.<ProcessBackendResponses_Ver_3>d__a.MoveNext()
   ...
   à RdvMedecins_01.LazyEagerLoading.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-PostgreSQL-01\LazyEagerLoading.cs:wiersz 23

Błędny kod to:


      using (var context = new RdvMedecinsContext())
      {
        // slot nr 0
        creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
}

Wiersz nr 1 wyjątku – zgłoszony błąd sugeruje, że chodzi o połączenie, ponieważ LEFT jest słowem kluczowym tego połączenia. Ponieważ w wierszu 4 powyższego kodu żądane jest natychmiastowe załadowanie zależności [Medecin] od encji [Creneau], EF wykonało połączenie tabel [CRENEAUX] i [MEDECINS]. Wydaje się jednak, że łącznik ADO.NET wygenerował nieprawidłowe polecenie SQL. Przepisujemy kod w następujący sposób:


      using (var context = new RdvMedecinsContext())
      {
        // slot nr 0
        creneau = context.Creneaux.Find(idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
        // wymuszamy załadowanie powiązanego lekarza
        // jest to możliwe, ponieważ nadal znajdujemy się w otwartym kontekście
        Medecin medecin = creneau.Medecin;
}
  • wiersz 4: wyszukujemy przedział bez połączeń;
  • wiersz 8: pobieramy brakującą zależność.

Działa. Ponownie zauważamy, że zmiana w SGBD ma wpływ na kod. W rzeczywistości to nie SGBD jest tutaj przyczyną, ale jego łącznik ADO.NET.

6.3. Architektura wielowarstwowa oparta na EF 5

Wracamy do naszego studium przypadku opisanego w akapicie 2.

Zaczniemy od zbudowania warstwy dostępu do danych [DAO]. W tym celu duplikujemy projekt konsoli VS 2012 [RdvMedecins-SqlServer-02] w [RdvMedecins-PostgreSQL-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-PostgreSQL-02];
  • w [4] nowy projekt nosi nazwę tego, który został usunięty. Zmienimy jego nazwę;
  • w pliku [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-PostgreSQL-01]. Oba projekty korzystają bowiem z tych samych szablonów.
  • w [8] – aktualne odniesienia projektu;
  • w pliku [9] dodano łącznik ADO.NET z projektu PostgreSQL za pomocą narzędzia NuGet.

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


  <!-- łańcuch połączenia z bazą danych -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- dostawca fabryczny -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </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-PostgreSQL-02].

Po wykonaniu tej czynności jesteśmy gotowi do przeprowadzenia testu warstwy [DAO]. Wcześniej należy jednak zadbać o wypełnienie bazy danych (program [Fill] z projektu [RdvMedecins-PostgreSQL-01]). Program testowy ulega awarii, zgłaszając następujący wyjątek:

2012/10/12 13:56:27:188 [INFO]  Spring.Context.Support.XmlApplicationContext - A
pplicationContext Refresh: Completed
Liste des clients :
Client [47,Mr,Jules,Martin,468]
Client [48,Mme,Christine,German,469]
Client [49,Mr,Jules,Jacquard,470]
Client [50,Melle,Brigitte,Bistrou,471]
Liste des médecins :
Medecin [42,Mme,Marie,Pelissier,472]
Medecin [43,Mr,Jacques,Bromard,497]
Medecin [44,Mr,Philippe,Jandot,510]
Medecin [45,Melle,Justine,Jacquemot,511]
L'erreur suivante s'est produite : RdvMedecinsException[3,GetCreneauxMedecin,Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne.]

Wiersz 13 – komunikat wskazuje, że błąd wystąpił w metodzie [GetCreneauxMedecin] warstwy [DAO]. Metoda ta ma następującą postać:


    // lista terminów wizyt danego lekarza
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista terminów
      try
      {
        // otwarcie kontekstu trwałości
        using (var context = new RdvMedecinsContext())
        {
          // pobieranie lekarza wraz z jego terminami
          Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
          // zwracanie listy terminów lekarza
          return medecin.Creneaux.ToList<Creneau>();
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

W wierszu 11 rozpoznajemy słowo kluczowe Include, które już wcześniej spowodowało awarię innego programu. Poprzedni kod można zastąpić następującym:


    // lista terminów danego lekarza
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista terminów
      try
      {
        // otwarcie kontekstu trwałości
        using (var context = new RdvMedecinsContext())
        {
          // zwraca listę terminów lekarza
          return context.Creneaux.Where(c => c.MedecinId == idMedecin).ToList<Creneau>(); 
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

Nowy kod wydaje się nawet bardziej spójny niż poprzedni. W każdym razie tym razem program testowy działa poprawnie.

Tworzymy plik DLL projektu tak samo, jak zrobiono to dla projektu [RdvMedecins-SqlServer-02], a następnie przenosimywszystkie pliki DLL z projektu w folderze [lib] utworzonym w [RdvMedecins-PostgreSQL-02]. Będą to odniesienia do projektu internetowego [RdvMedecins-PostgreSQL-03], który pojawi się w dalszej części.

  

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

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

  • do [2], przy użyciu programu VS 2012 Express for the Web otwieramy rozwiązanie z folderu [RdvMedecins-PostgreSQL-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-PostgreSQL-02].

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