Trochę teorii
Dla tych, którzy nie spotkali się jeszcze z pojęciem O/RM
krótkie wyjaśnienie. Skrót oznacza Object-relational mapping. Narzędzia tej
klasy służą do odwzorowania relacyjnej bazy danych, na bliższe każdemu
programiście obiekty. Mapowanie odbywa się najczęsciej jako odworowanie definicji
klas na strukturę tabel, a każdy rekord takich tabel reprezentowany jest przez
konkretny obiekt. Jest kilka powodów, dla których warto zastanowić się nad
takim rozwiązaniem. W rozwiązaniach nie korzystających z O/RM, musimy używać
dwóch zupełnie różnych języków programowania, a kod, gdzie język obiektowy
przeplata się z SQL’em, jest ciężki do utrzymania i rozwijania, nie wspominając o debuggowaniu. Narzędzia klasy O/RM pozwalają uprościć i
zautomatyzować proces rozwijania i utrzymywania aplikacji bazodanych. Jednym z takich rozwiązań jest Sooda – Simple
Object Oriented Data Access, autorstwa Jarka Kowalskiego.
Sooda oparta jest na mechanizmie generacji kodu (o tym trochę
więcej za chwilę). W naturalny sposób odwzorowuje obiekty na tabele, wspiera
również kolekcje mapując je na relacje 1-N i M-N, dodatkowo umożliwiając
dziedziczenie i polimorfizm. Pozwala także osiągnąć wysoką wydajność, m.in poprzez
zaawansowane cache’owanie obiektów, "leniwe
ładowanie" (ang. lazy loading), itp. Cała konfiguracja natomiast przechowywana
jest w jednym miejscu, co jest dużą zaletą przy np. zapoznawaniu się z
aplikacją opartą na Soodzie.
Do tej pory największą bolączką Soody, był brak kompletnej
dokumentacji. Od paru dni na stronie domowej Soody, jest kompletna i bardzo
szczegółowa dokumentacja – link możecie znaleźć na końcu artykułu. Mam nadzieję, że
artykuł ten szczegółowo wyjaśni jak skonfigurować aplikację, żeby zaczęła
korzystać z Soody i krok po kroku wprowadzi Was w świat O/RM. Ponieważ
najlepiej uczyć się na przykładzie, stworzymy prostą ‘Wypożyczalnię Video’.
Osoby obecne na pierwszym spotkaniu Warszawskiej Grupie .NET z pewnością pamiętają ten przykład. Zacznijmy
więc od początku.
Konfiguracja aplikacji
Na początek przyda nam się plik instalacyjny Soody, lub też
źródła (odpowiednie adresy na końcu). Mając przykładową bazę
danych:

chcielibyśmy otrzymać strukturę klas odpowiadającą naszej
bazie danych taką, jak przedstawia nam diagram klas:

Podczas
konfiguracji projektu będziemy korzystać z aplikacji SoodaStubGen, dostępnej w
pakiecie instalacyjnym Soody. Ten mały program ma za zadanie z dwóch plików
konfiguracyjnych *.soodaproject i schematu bazy danych w pliku XML, wygenerować
klasy odpowiadające naszej bazie. Użyteczną funkcją tego programu, jest to, że
potrafi zaktualizować projekt Visual Studio, dodając do odpowiedniego pliku
*.csproj wygenerowane pliki. Kod wygenerowany przez Soodę (może to być C#,
VB.NET, Boo i teoretycznie każdy język wspierający generację kodu ze
struktur CodeDom) znajduje się głównie w tzw. Stubs’ach – zachęcam do
zerknięcia, co w tym pliku się znajduje. Dodatkowo SoodaStubGen generuje puste
klasy, w których będziemy umieszczać naszą logikę biznesową. Powyżej opisany
schemat tworzenia kodu wygląda mniej więcej tak:

Najwięcej pracy będziemy mieli przy tworzeniu pliku
SoodaSchema.xml, odpowiedzialnego za odwzorowanie klas na tabele w bazie danych. Pierwszą rzeczą jaką musimy umieścić w tym
pliku, jest definicja źródła danych – w tej chwili jedynym wspieranym źródłem
jest relacyjna baza danych. Aby zdefiniować źródło danych należy na początku
pliku SoodaSchema.xml dodać element:
<datasource name="default" type="Sooda.Sql.SqlDataSource" />
Podstawową tabelą, którą będziemy używać w naszej
wypożyczalni jest tabela Video. Zmapujmy więc ją na interesującą nas
klasę tak jak poniżej (pełne
mapowanie na końcu artykułu):
1 <class name="Video">
2 <table name="Video">
3 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
4 <field name="Title" type="String" nullable="false" />
5 <field name="Category" type="Integer" references="VideoCategory" prefetch="1" />
6 <field name="Status" type="Integer" references="VideoStatus" prefetch="1" />
7 <field name="RentedOutTo" dbcolumn="rented_out_to" type="Integer" references="Customer" />
8 <field name="RentedDate" dbcolumn="rented_date" type="DateTime" nullable="true" />
9 <field name="ReturnedDate" dbcolumn="returned_date" type="DateTime" nullable="true" />
10 <field name="DirectedBy" dbcolumn="director" type="Integer" references="Artist" prefetch="1" />
11 <field name="YearOfProduction" dbcolumn="year_of_production" type="Integer" />
12 </table>
13 </class>
Podstawowym elementem jest class oparty na jednej lub
wielu tabelach (możliwe jest również oparcie wielu klas na jednej tabeli) w
bazie danych o nazwie „Video” (<table name="Video">).
Każdej kolumnie w tabeli odpowiada jedna właściwość:
<field name="RentedDate" dbcolumn="rented_date" type="DateTime" nullable="true" />
Każda taka właściwość
ma oznaczony typ, zazwyczaj taki jak typ w bazie danych, nazwę –
niekoniecznie taką samą i atrybut mówiący czy kolumna może być pusta. Poniżej wymieniono
najczęściej spotykane typy pól (są też inne np. typ Image – zachęcam do lektury
dokumentacji)
· String
- dodatkowo należy wyspecyfikować parametr size dla właściwości
· Integer
· DateTime
· BooleanAsInteger
– bardzo ciekawy typ, w bazie danych reprezentowany jako int (o wartości 1 lub
0), natomiast właściwość będzie przedstawiona jako typ boolean.
Przynajmniej jedna z właściwości musi być kluczem głównym (primaryKey="true"). Sooda
wspiera również złożone klucze główne.
Jak łatwo na pełnym mapowaniu część właściwości ma zdefiniowany
parametr references. Użycie
go, spowoduje wygenerowanie właściwości
będącej referencją do innej klasy – czyli tak naprawdę klucza obcego np.
w klasie Video, właściwość Status jest
tak naprawdę referencją do klasy VideoStatus.
Sooda wspiera również kolekcje jeden do wielu i wiele do wielu.
Te pierwsze definiuje się poprzez element:
·
<collectionOneToMany> - reprezentowana jako referencja i kolekcja (np.
dla klasy Video zdefiniowana jest kolekcja przetrzymująca hisorię wypożyczeń
(VideoHistory)
·
<collectionManyToMany> - reprezentowana
jako para kolekcji - dla Video jest to kolekcja aktorów, przypisanych do filmu
(Actors2Video). W odróżeniu od klas, relacja jest przedstawiona w pliku
mapującym, jako <relation>, gdzie klucze główne są referencjami
Przypuśćmy, że chcielibyśmy, aby w naszej aplikacji oprócz danych klientów,
przetrzymywać również dane pracowników, którzy mogliby wypożyczać filmy po
specjalnych cenach. Aby nie dublować funkcjonalności w dwóch różnych obiektach
(klient i pracownik). Najrozsądniejszym rozwiązaniem jest dziedziczenie klasy
pracownik z klasy klient, lub stworzenie klasy abstrakcyjnej, z której
dziedziczyłyby te dwie. Sooda wspomaga nas przy takich zadaniach, umożliwiając
zdefinowanie pola (selektora), które będzie używane do odróżniania konkretnych
podklas danej klasy. Najlepiej to zrozumieć na przykładzie naszej aplikacji.
Klasa Person ma zdefniowany selektor: subclassSelectorField="Type". Na podstawie wartości pola Type,
rekord z bazy danych będzie zwrócony jako obiekt tej, albo innej klasy. Klasy Customer
i Employee dziedziczą z klasy Person (inheritFrom="Person") na
podstawie różnych wartości pola Type (wartość zdefiniowana przez atrybut subclassSelectorValue klasy).
Klasa , która nie ma zdefinowanego subclassSelectorValue, będzie klasą
abstrakcyjną.
Ostatnią rzeczą, o której chciałbym wspomnieć , są obiekty
wyróźnione. Prawdziwą udręką podczas rozwijania aplikacji jest moment, gdy
podczas przeglądania zapytania do bazy danych, nie pamiętamy co oznacza wartość
1 czy 2 w polu Status – trzeba przegrzebywać się przez ustalenia, dokumentację,
czasem przeglądać zawartość bazy danych. Obiekty wyróżnione mają nam w tym
pomóc. Wystarczy zdefiniować dla klasy odpowiednie elementy <const> mówiące, że rekord z tabeli, np. VideoStatus o kluczu głównym 1 oznacza film wypożyczony
(RentedOut). Od tej pory będzie można
poprzez proste wywołanie w kodzie, wyciągnąć ten rekord, w celu użycia go w
zapytaniu, porównaniu, itp – ale o tym za chwilę.
Generowanie kodu
Mamy już więc plik
mapujący. W tym momencie będziemy potrzebować SoodaStubGen.exe. Zadaniem tego
programu jest wygenerowanie na podstawie przygotowanego przez nas pliku,
odpowiednich klas, z właściwościami, kolekcjami, obiektami wyróżnionymi itp.
oraz wygenerowanie pustych klas, w których umieszczać będziemy całą funkcjonalność.
SoodaStubGen ma wiele parametrów wywołania. Zamiast podawania długiej komendy
łatwiej będzie przygotować plik
*.soodaproject – mały pliku xml, w którym zostaną umieszczone te parametry. Dzięki
niemu będzie nam łatwiej przekonfigurowywać aplikację. Zawartość przykładowego
pliku wygląda np tak:
1 <?xml version="1.0" encoding="utf-8"?>
2 <sooda-project xmlns="http://www.sooda.org/schemas/SoodaProject.xsd">
3 <schema-file>SoodaSchema.xml</schema-file>
4 <language>c#</language>
5 <output-namespace>VideoRental</output-namespace>
6 <output-path>.</output-path>
7 <nullable-representation>SqlType</nullable-representation>
8 <not-null-representation>Raw</not-null-representation>
9 <with-indexers>false</with-indexers>
10 <with-typed-queries>true</with-typed-queries>
11 <embedded-schema-type>Binary</embedded-schema-type>
12 <external-projects>
13 <project type="vs2005" file="VideoRental.csproj"/>
14 </external-projects>
15 </sooda-project>
Najważniejsze z parametrów:
-
schema-file – nazwa pliku mapującego;
-
language – język w którym kod zostanie
wygenerowany (C# jest moim zdaniem najlepszym rozwiązaniem, gdyż tylko ten
język pozwala na zastosowanie zapytań typowanych, o których za chwilkę opowiem);
-
output-namespace – przestrzeń nazw dla
wygenerowanych klas;
-
output-path – miejsce, w którym pliki zostaną
zapisane;
-
nullable-representation – reprezentacja
właściwości mogących przyjmować wartość NULL. Możliwe jest użycie:
o
Boxed – w tym przypadku tracimy informacje o
typie, a właściwości będą zwracane jako typ object,
o
SqlType – użycie specjalnych typów z
przestrzeni nazw System.Data.SqlTypes,
o
Raw – dane są przetrzymywane jako
‘standardowe’ typy,
o
Nullable – użyte będą typy Nullable z .NET 2.0,
o
RawWithIsNull – rozszerzenie opcji Raw. Dla
każdej właściwości zostaną wygenerowane metody IsNull zwracające wartość prawda
/ fałsz.
- not-null-representation – podobnie jak wyżej,
tym razem dla właściwości nienullowalnych;
- with-indexers – włącza lub wyłącza, indeksery
dla wygenerowanych list;
-
with-typed-queries – włącza lub wyłącza
używanie zapytań typowanych;
-
external-projects – pozwala zaktualizować
pliki projektów VS2005 i VS2003 o wygenerowane pliki
Teraz musimy uruchomić SoodaStubGen podając jako parametr
nazwę naszego pliku *.soodaproject. Zalecane jest ustawienie w projekcie Visual
Studio w pre-build-event command line
wywołania SoodaStubGen np. w ten sposób:
$(SolutionDir)\Sooda\SoodaStubGen.exe
$(ProjectDir)VideoRental.soodaproject
Spowodouje to wywołanie SoodaStubGen z podktalogu Sooda, a
jako parametr należy podajć plik VideoRental.soodaproject z katalogu projektu Wygenerowanie
w ten sposób kodu, spowoduje wyświetlenie ostrzeżenia w Visual Studio, że plik
projektu został zmieniony i należy go ponownie załadować. Dwie rzeczy, na które
warto zwrócić uwagę:
1. SoodaStubGen
rozpoznaje, czy plik mapujący się zmienił – wówczas generuje jeszcze raz plik /
pliki szkieletowe. Nie rozpoznaje jednak czy zmienił się plik *.soodaproj. W
przypadku, gdy zmienimy coś w tym pliku, najlepiej jest zapisać plik
mapującytak, aby zmieniła się data ostatniej modyfikacji pliku.
2. SoodaStubGen
potrafi dodawać nowo wygenerowane pliki do pliku projektu Visual Studio –
jednak, co jest oczywiste, nie potrafi tych plików usuwać, w momencie gdy
zrezygnujemy z jakiejś klasy lub zmienimy jej nazwę. Wówczas musimy taki plik
usunąć ręcznie.
Zostały nam jeszcze dwie rzeczy do zrobienia - zdefiniowane connectionString
i wskazanie, z jakiego schematu mapowania będzie korzystać Sooda. Wskazanie
łańcucha połączeniowego może być zrobione na wiele sposobów - najprostszym
będzie wykorzystanie pliku konfiguracyjnego App.config, lub specjalnego pliku
Sooda.config.xml. W pierwszym przypadku wystarczy App.config uzupełnić o
poniższą zawartość:
<!-- Wersja dla App.config -->
1 <configuration>
2 <appSettings>
3 <add key="default.connectionString"
4 value="Integrated Security=true;Server=.;Database=MyDatabase"/>
5 <add key="default.sqlDialect" value="mssql"/>
6 </appSettings>
7 </configuration>
<!-- Wersja dla Sooda.config.xml -->
1
2 <configuration>
3 <default>
4 <connectionString>
5 Integrated Security=true;Server=.;Database=MyDatabase
6 </connectionString>
7 <sqlDialect>
8 mssql
9 </sqlDialect>
10 </default>
11 </configuration>
W drugim przypadku możemy wszystkie Soodowe
ustawienia trzymać w oddzielnym pliku xml. Aby go użyć, należy w pliku
AssemblyInfo.cs dodać tę linijkę:
[assembly: SoodaConfig(XmlConfigFileName
= "Sooda.config.xml")]
Na Rysunku powyżej pokazana jest przykładowa zawartość tego pliku,
ustawiająca connectionString. W samej konfiguracji możemy definiować jeszcze
wiele parametrów dla naszej aplikacji, jak dialekt sql (specyficzne dla silnika
bazodanego generowanie sql'a), rodzaj bazy danych, itp. Wskazanie schematu
mapowania jest potrzebne dla prawidłowego działania Soody. Znów mamy możliwość
zrobienia tego na kilka sposobów. Ponownie najprościej będzie dodać do
AssemblyInfo.cs tę linijkę:
[assembly: SoodaStubAssembly(typeof(MojaPrzestrzenNazw._DatabaseSchema))]
Programowanie z użyciem Soody
W momencie wygenerowania
klas, pozostaje nam tylko zakodowanie funkcjonalności naszej aplikacji. Na
początku może zróbmy coś prostego: wypiszmy
na ekran wszystkie filmy video, które mamy w bazie(skrypt zakładający bazę i
wypełniający ją przykładowymi danymi znajdziecie pod jednym z linków podanych na końcu). W tym celu użyjemy metody, którą posiadają wszystkie obiekty
Soodowe: GetList. Po pierwsze, aby używać Soody, musimy stworzyć transakcję –
wszystkie obiekty żyją tylko w obrębie transakcji, wszelkie operacje na
obiektach po jej zakończeniu mogą skutkować wyjątkiem. Utworzenie transakcji
jest proste – wystarczy użyć poniższej składni:
using (SoodaTransaction
t = new SoodaTransaction())
{ /* nasz kod */ }
Jak więc z tabeli Video pobrać
wszystkie rekordy i wypisać np tytuł razem z rokiem produkcji? Nic prostszego:
using (SoodaTransaction
t = new SoodaTransaction())
{
// SQL:
select * from Video;
VideoList
list = Video.GetList(true);
foreach (Video v in list)
{
Console.WriteLine("Tytuł
{0}, Rok produkcji: {1}\n",
v.Title, v.YearOfProduction);
}
}
Wykorzystaliśmy tutaj
właśnie metodę GetList. Przekazany parametr true pobierze wszystkie rekordy bez jakiegokolwiek zawężania listy.
Każda klasa Soodowa ma odpowiadającą mu klasę KlasaList, posiadającą
standardowe metody spotykane w listach, takie jak Add, Remove, Contains, a
także kilka innych użytecznych metod.
Załóżmy, że chcielibyśmy
zawęzić nasze poszukiwania do filmów wypożyczonych, które mają w tytule
‘Harry’. Wystarczy znów użyć metody GetList, tym razem jako parametr podając
wyrażenie zawężające listę:
VideoList list = Video.GetList(
VideoField.Title.Like("Harry%") &&
VideoField.Status
== VideoStatus.RentedOut);
Jako parametr użyliśmy
tutaj zapytań typowanych (dostępnych tylko dla języka C#). Podobnie jak w
przypadku list, tak i tutaj, dla każdej klasy generowane są dodatkowe
KlasaField – używane właśnie w tego rodzaju zapytaniach. Budowanie zapytania,
wygląda jak budowanie zwykłego warunku logicznego. Co ciekawe, w dowolny sposób
możemy przechodzić po właściwościach obiektów, np.warunek:
VideoField.Status == VideoStatus.RentedOut (zwróćcie uwagę na
użycie obiektu wyróżnionego VideoStatus.RentedOut)
można również zapisać w
ten sposób:
VideoField.Status.Name == "Rented Out".
W przypadku zapytań
typowanych w bonusie dostajemy IntelliSense w VS podczas budowania zapytań
(przy dostępie do metod i właściwości, IntelliSense jest oczywiście dostępny
dla każdego języka), a także jak sama nazwa wskazuje kontrolę typów na poziomie
kompilacji. W innych językach, które wspiera Sooda, jak też i C#, można użyć
zapytań nietypowanych:
1 // do zapytań nietypowanych używamy SoodaWhereClause
2 VideoList list = Video.GetList(new SoodaWhereClause(
3 "Title like {0} and Status == {1}",
4 "HarryPotter", VideoStatus.Available)
5 );
Zróbmy coś więcej.
Wypiszmy wszystkie filmy, w których gra Emma Watson. Można zrobić to na dwa
sposoby. Pierwszy sposób to pobranie odpowiedniego obiektu typu Artist i
wyświetlenie wszystkich filmów z kolekcji powiązanej z tą aktorką. Problem jest
taki, że w tym momencie w pliku mapującym dla klasy Artist nie mamy
zdefiniowanej takiej kolekcji. Musimy dodać tę definicję kolekcji do klasy Artist:
<collectionManyToMany name="Videos" relation="Actor2Video" masterField="1" />
Po skompilowaniu
projektu, możemy użyć następującego kodu:
using (SoodaTransaction
t = new SoodaTransaction())
{
// Szukamy Emmy Watson
Artist emmaWatson = Artist.LoadSingleObject(
ArtistField.Name == "Emma Watson");
foreach (Video
v in emmaWatson.Videos)
// wypisanie Danych
}
LoadSingleObject pobiera
z bazy danych pojedynczy obiekt. Jeżeli żaden obiekt nie będzie odpowiadał
podanym warunkom, zostanie zwrócony null, jeżeli więcej niż jeden - zostanie zgłoszony
wyjątek. Drugim sposobem jest pobranie tych obiektów typu Video, które w kolecji aktorów, zawierają
szukanego przez nas artystę:
Artist emmaWatson = Artist.LoadSingleObject(
ArtistField.Name == "Emma Watson");
VideoList list = Video.GetList(
VideoField.Actors.Contains(emmaWatson));
Niżej można zobaczyć
jeszcze bardziej rozbudowane zawężenie listy:
1 VideoList list = Video.GetList(
2 VideoField.YearOfProduction >= 2000
3 && VideoField.YearOfProduction <= 2004
4 && VideoField.Status == VideoStatus.RentedOut
5 && VideoField.RentedOutTo.Email.Like("%@customer.com")
6 && VideoField.DirectedBy.Name.Like("Chris%")
7 && VideoField.History.Count > 1);
Dużo mówimy tu o pobieraniu obiektów, a co z
tworzeniem nowych? Chcemy dodać nowy film, razem z aktorami. Nic prostszego –
wystarczy stworzyć tylko obiekt w transakcji Soodowej, a następnie taką
transakcje zapisąc, przy użyciu metody Commit(), tak jak tutaj:
1 using (SoodaTransaction t = new SoodaTransaction())
2 {
3 Artist emmaWatson = Artist.LoadSingleObject(ArtistField.Name == "Emma Watson");
4
5 Video v = new Video();
6 v.Title = "Harry Potter i Czara Ognia";
7 v.Status = VideoStatus.Available;
8 v.YearOfProduction = 2005;
9 v.Actors.Add(emmaWatson);
10
11 // ten warunek zwróci nam właśnie utworzony film
12 VideoList v = v.GetList(VideoField.Title == "Harry Potter i Czara Ognia");
13
14 // commitujemy transakcję
15 t.Commit();
16 }
Bardzo interesujące jest to, że przed zapisaniem
transakcji , możemy utworzony obiekt już pobrać. Z pewnością część z Was zauważyła,
że nie podajemy jawnie klucza głównego dla tego filmu. Sooda domyślnie sama
generuje poprawne klucze główne, przechowując je w specjalnej tabeli KeyGen o
strukturze takiej jak na tutaj:
Uaktualnienie rekordu, jest równie proste
– wystarczy nadać nowe wartości właściwościom.
Natomiast aby usunąć biekt należy wywołać metodę MarkForDelete().
Krótkie podsumowanie
Nie zdołaliśmy nawet zacząć się przekopywać przez
funkcjonalność drzemiącą w Soodzie. Nie wykorzystaliśmy np. triggerów – metod
pełniących tę samą funkcję co triggery w bazie danych. Zainteresowanych, a mam
nadzieję, że tacy będą po przeczytaniu tego artykułu, odsyłam do dokumentacji.
Na sam koniec chciałem przedstawić pomocnika, który ma automatycznie tworzyć
plik mapujący, plik konfiguracyjny *.soodaproj i zaktualizowanie projektu
Visual Studio. Po zainstalowaniu Soody 1.0 w Menu Start, będziemy mogli dodać
Soodę do projektu Visual Studio 2005. Po wybraniu tej opcji, uruchomi się mały
wizard, prowadzący nas za rękę po konfiguracji Soody

Po co więc ten rozbudowany artykuł? Program nie wygeneruje pliku
mapującego z dziedziczeniem, czy obiektami wyróżnionymi – a z pewnością warto
wiedzieć jak ten plik zmieniać. Życzę miłej zabawy i pracy z Soodą. Nie
zniechęcajcie się – czas poświęcony na konfigurację aplikacji i poznanie Soody zostanie Wam zwrócony z nawiązką
podczas tworzenia aplikacji.
Użyteczne linki:
1. Strona domowa Soody z pełną dokumentacją i plikami instalacyjnymi: http://sooda.org
2. Prezentacja Jarka Kowalskiego na temat Soody razem z przykładowym kodem: http://svn.sooda.org/repos/sooda/trunk/presentations/
3. Repozytorium Soody: http://svn.sooda.org/repos/sooda/
Pełny schemat mapowania:
1 <schema xmlns="http://www.sooda.org/schemas/SoodaSchema.xsd">
2 <datasource name="default" type="Sooda.Sql.SqlDataSource" />
3
4 <class name="Video">
5 <table name="Video">
6 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
7 <field name="Title" type="String" nullable="false" />
8 <field name="Category" type="Integer" references="VideoCategory" prefetch="1" />
9 <field name="Status" type="Integer" references="VideoStatus" prefetch="1" />
10 <field name="RentedOutTo" dbcolumn="rented_out_to" type="Integer" references="Customer" />
11 <field name="RentedDate" dbcolumn="rented_date" type="DateTime" nullable="true" />
12 <field name="ReturnedDate" dbcolumn="returned_date" type="DateTime" nullable="true" />
13 <field name="DirectedBy" dbcolumn="director" type="Integer" references="Artist" prefetch="1" />
14 <field name="YearOfProduction" dbcolumn="year_of_production" type="Integer" />
15 </table>
16 <collectionOneToMany name="History" class="VideoHistory" foreignField="Video" />
17 <collectionManyToMany name="Actors" relation="Actor2Video" masterField="0" />
18 </class>
19
20 <class name="Person"
21 subclassSelectorField="Type">
22 <table name="Person">
23 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
24 <field name="Type" type="Integer" nullable="false" />
25 <field name="Name" type="String" nullable="false" />
26 </table>
27 </class>
28
29 <class name="Employee"
30 inheritFrom="Person"
31 subclassSelectorValue="1">
32 <table name="Employee">
33 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
34 <field name="Login" type="String" nullable="false" />
35 </table>
36 <const name="Edward" key="1" />
37 <const name="Admin" key="2" />
38 </class>
39
40 <class name="Customer"
41 inheritFrom="Person"
42 subclassSelectorValue="2">
43 <table name="Customer">
44 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
45 <field name="Email" type="String" nullable="false" />
46 <field name="Address" type="String" nullable="false" />
47 </table>
48 </class>
49
50 <class name="VideoHistory">
51 <table name="VideoHistory">
52 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
53 <field name="Video" dbcolumn="video_id" type="Integer" references="Video" nullable="false" />
54 <field name="Person" dbcolumn="person_id" type="Integer" references="Person" nullable="false" onDelete="Cascade"/>
55 <field name="TimeStamp" dbcolumn="time_stamp" type="DateTime" nullable="true" />
56 <field name="ActionType" dbcolumn="action_type" type="Integer" references="VideoHistoryActionType" nullable="false" />
57 <field name="Comment" type="String" nullable="false" />
58 </table>
59 </class>
60
61 <class name="VideoCategory">
62 <table name="VideoCategory">
63 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
64 <field name="Name" type="String" nullable="false" />
65 </table>
66 </class>
67
68 <class name="VideoStatus">
69 <table name="VideoStatus">
70 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
71 <field name="Name" type="String" nullable="false" />
72 </table>
73 <const name="RentedOut" key="1" />
74 <const name="Available" key="2" />
75 <const name="Damaged" key="3" />
76 <const name="Lost" key="4" />
77 </class>
78
79 <class name="VideoHistoryActionType">
80 <table name="VideoHistoryActionType">
81 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
82 <field name="Name" type="String" nullable="false" />
83 </table>
84 <const name="RentedOut" key="1" />
85 <const name="Returned" key="2" />
86 <const name="Lost" key="3" />
87 <const name="Damaged" key="4" />
88 <const name="Created" key="5" />
89 <const name="Deactivated" key="6" />
90 </class>
91
92 <class name="Artist">
93 <table name="Artist">
94 <field name="Id" type="Integer" primaryKey="true" nullable="false" />
95 <field name="Name" type="String" nullable="false" />
96 </table>
97 <const name="ChuckNorris" key="1" />
98 <const name="ValKilmer" key="2"/>
99 <const name="OmarSharif" key="3"/>
100 </class>
101
102 <relation name="Actor2Video">
103 <table name="Actor2Video">
104 <field name="Actor" dbcolumn="actor_id" type="Integer" references="Artist" />
105 <field name="Video" dbcolumn="video_id" type="Integer" references="Video" />
106 </table>
107 </relation>
108
109 </schema>