Inżynieria Programowania - Lab3
Transkrypt
Inżynieria Programowania - Lab3
Inżynieria Programowania Laboratorium 4 Biblioteka Połaczeniowa Paweł Paduch [email protected] 20-04-2013 Rozdział 1 Wstęp Na dzisiejszych zajęciach zajmiemy się tworzeniem biblioteki kodu obsługującego bazę danych. Zrealizujemy podstawową funkcjonalność, dodawania, wyszukiwania i edycji wybranych danych. Postaramy się zrealizować jedną z trudniejszych rzeczy, mianowicie dodawanie propozycji rezerwacji z jednoczesnym sprawdzaniem czy nie koliduje z istniejącymi rezerwacjami. Do implementacji posłuży nam środowisko Visual Studio 2010, platformą bazodanową będzie SQL Server 2008. Posłużymy się modelem programowania ADO.NET Entity Framework. W krótkim wyjaśnieniu czym jest Entity Framework (EF) posłużę się cytatem z książki A. Troelsena [1]. „Nadrzędnym celem EF jest umożliwienie komunikacji z relacyjnymi bazami danych za pomocą modelu obiektowego skojarzonego bezpośrednio z biznesowymi obiektami aplikacji. Nie musimy na przykład traktować porcji danych jako kolekcji wierszy i kolumn, ale możemy operować na kolekcji obiektów ze ścisłą kontrola typów, nazywanych encjami.” Dzięki kompatybilności obiektów encji z LINQ można stosować do nich ten język zapytań. Zapytanie LINQ jest tłumaczone przez silnik wykonawczy EF na zapytanie SQL. Oprócz wspomnianej wcześniej książki, pomocne jak zwykle będą strony z dokumentacją. Poniżej ogólnie o bazach danych i platformie .Net 4.0 http://msdn.microsoft.com/en-us/library/vstudio/951h6we4(v=vs.100).aspx A tu konkretnie o modelu Entity Framework. http://msdn.microsoft.com/en-us/library/vstudio/bb399572(v=vs.100).aspx 1 Rozdział 2 Jeszcze drobne poprawki w bazie Ostatnio zapomnieliśmy o tym, że login osoby powinien być unikalny. Aby zapewnić unikalność z poziomu bazy danych należy, założyć indeks na polu Login w tabelce Osoby. W Serwer Explorerze klikamy dwukrotnie tabelę Osoby w celu otworzenia jej do edycji. Z menu kontekstowego tabelki wybieramy Indexes/Keys. Klikamy guzik Add i przechodzimy do edycj nowo powstałego indeksu. W polu Columns wybieramy nazwę naszej kolumny Login a w polu Type wybieramy Unique Key. (rys. 2.1) Można też ustawić nazwę na Index Login. Po zmianach należy je zapisać. Rysunek 2.1: Indeksy i klucze Jak się później można było by przekonać, używanie typu nchar niesie ze sobą pewne zalety ale też i wady. Zaletą jest, lepsza wydajność, nie trzeba alokować pamięci bo typ ten na stałe bierze zadaną liczbę znaków. Wada objawia się przy 2 wyświetlaniu. Do wartości zawartych w typie nchar dokładane są spacje. można oczywiście korygować to wywołując metodę Trim jednak przy każdym użyciu robi się to uciążliwe. Stąd decyzja o zamianie wszystkich typów nchar na typy varchar. Maksymalna długość pozostanie taka sama. 3 Rozdział 3 Biblioteka dostępu do bazy Aby umożliwić wielokrotne wykorzystanie kodu przez różne aplikacje (lub różne wersje tej samej aplikacji, np. ASP.NET, WPF) stworzymy osobną bibliotekę dzięki której ułatwimy sobie dostęp do naszej bazy danych. 3.1 Nowy projekt Zakładamy nowy projekt podając jak typ projektu ClassLibrary i zmieniamy nazwę na RezerwacjeLib. Jest to ważne, ponieważ nazwa ta będzie też domyślną przestrzenią nazw. Do projektu dodajemy (z menu kontekstowego projektu Add→New Item) ADO.NET Entity Data Model nazywając go Rezerwacje.edmx (rys. 3.1). Rysunek 3.1: Dodanie EDM Rezerwacje Wybieramy Generate From Database (jeżeli nie jesteśmy połączeni należy się wcześniej połączyć), na następnych oknach kreatora wybieramy połączenie z naszą bazą danych oraz zaznaczamy opcję Save entity connection setings in 4 App.config as podając nazwę rezerwacjeEntities (rys.3.2). Zaznaczamy, że chce- Rysunek 3.2: Dodawanie EDM połaczenie my zaimportować tabele oraz ustawiamy nazwę Model namespace na RezerwacjeModel (rys. 3.3). 3.2 Klasa Role Stwórzmy sobie klasę Role do zarządzania uprawnieniami. Możliwe, że nazwa Uprawnienia bardziej by nam się kojarzyła, jednak jest ona już użyta w przestrzeni nazw RezerwacjeEntity. Możnab y też wykorzystać fakt, że wygenerowane klasy w RezerwacjeEntity są częściowe (partial) i rozszerzyć ich funkcjonalność, ale na chwilę obecną stworzymy sobie swoje dodatkowe klasy. Z menu kontekstowego projektu wybieramy Add→New Item→Class ustawiamy nazwę na Role.cs. W klasie tej dodajemy nową przestrzeń nazw dopisując na początku pliku using System.Data;. Do klasy dodać należy też obiekt kontekstu encji rezerwacjeEntities context = new rezerwacjeEntities();. Tworzymy sobie 3 podstawowe metody znajdzUprawnieniePoNazwie, dodajUprawnienie oraz modyfikujUprawnienie. Metoda usunUprawnienie nie będzie nam potrzebna, gdyż w działającej bazie danych nie przewidujemy takiej opcji. Kod 3.1 pokazuje nam przykładową implementację. 5 Rysunek 3.3: Dodawanie EDM połaczenie 6 Listing 3.1: Klasa Role.cs 1 2 3 4 5 using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Data; 6 7 8 9 10 11 namespace RezerwacjeLib { public class Role { rezerwacjeEntities context = new rezerwacjeEntities(); 12 13 14 15 16 17 18 19 20 21 /// <summary> /// Wyszukaj uprawnienie po jego nazwie /// </summary> /// <param name="nazwa">Nazwa uprawnienia</param> /// <returns>Jeżeli istnieje to zwróci znalezione uprawnienie</returns> public Uprawnienia znajdzUprawnieniePoNazwie(string nazwa) { return context.Uprawnienia.Where("it.Nazwa='" + nazwa + "'").←FirstOrDefault(); } 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 /// <summary> /// Dodaje nowe uprawnienie /// </summary> /// <param name="nazwa">Krótka nazwa nowego uprawnienia</param> /// <param name="opis">Dodatkowy ewentualny opis</param> /// <returns>Zwraca nowo dodane uprawnienie lub null w przypadku ←niepowodzenia</returns> public Uprawnienia dodajUprawnienie(string nazwa,string opis) { Uprawnienia u = new Uprawnienia() { Nazwa = nazwa, Opis = opis}; context.Uprawnienia.AddObject(u); try { //zapisz wszystkie zmiany w fizycznej bazie //i zresetuj śledzenie zmian w obiekcie kontekstu context.SaveChanges(); } catch (Exception ex) { //nie udalo sie wstawic wiec usuwamy context.Uprawnienia.DeleteObject(u); Console.WriteLine("Nie udało się dodać nowego uprawnienia: "+ex.←Message); Console.WriteLine(ex.InnerException.Message); return null; } Console.WriteLine("Dodano nowe uprawnienie: id={0} {1} {2}", u.←IdUprawnienia, u.Nazwa, u.Opis); return u; } 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 /// <summary> /// Modyfikacja uprawniania należy wyszukać uprawnienie zmienić mu pola i ←wywołać tę metodę podając zmienione uprawnienie jako parametr. /// Identyfikatora nie ruszać! /// </summary> /// <param name="upr">Uprawnienie ze zmienionymi nowymi polami</param> /// <returns></returns> public bool modyfikujUprawnienie(Uprawnienia upr) { EntityKey key = new EntityKey("rezerwacjeEntities.Uprawnienia", "←IdUprawnienia", upr.IdUprawnienia); Uprawnienia u = (Uprawnienia)context.GetObjectByKey(key); if (u != null) { u.Nazwa = upr.Nazwa; u.Opis = upr.Opis; try { 7 67 68 69 70 71 72 Message); 73 74 75 76 77 78 79 80 81 82 83 } } context.SaveChanges(); } catch (Exception ex) { //nie udalo sie Console.WriteLine("Nie udało się zmienić uprawnienia: " + ex.←Console.WriteLine(ex.InnerException.Message); return false; } Console.WriteLine("Zmieniono uprawnienie o id={0} na {1} {2}", u.←IdUprawnienia, u.Nazwa, u.Opis); return true; } Console.WriteLine("Nie znaleziono uprawnienia do modyfikacji"); return false; } 8 Rozdział 4 Testowanie nowej biblioteki W celu sprawdzenia czy powyższe metody w klasie Role działają napiszemy szybko bardzo prostą aplikację konsolową. W tym celu otwieramy sobie nowe Visual Studio (tak będziemy mieć otwarte oba programy jednocześnie), tworzymy nowy projekt typu Console Application. Podłączamy bibliotekę poprzez dodanie referencji do niej. Z menu kontekstowego projektu wybieramy Add Reference klikamy na zakładkę Browse i nawigujemy do katalogu w którym znajduje się projekt RezerwacjeLib. Wchodzimy do katalogu RezerwacjeLib/obj/Debug i wybieramy plik RezerwacjeLib.dll (rys. 4.1). Jeżeli pliku nie znajdziemy to znaczy, że nie przekompilowaliśmy biblioteki (klawisz F6). Należy też dodać referencję do System.Data.Entity tylko z zakładki .Net. Żeby nasza aplikacja łączyła się z tą samą bazą do której stworzona jest biblioteka RezerwacjeLib należy jeszcze do naszego projektu dodać plik konfiguracji App.config. Jest tam zapisany łańcuch połączenia. Robimy to za pomocą menu kontekstowego projektu Add→Existing Item i nawigujemy do katalogu z projektem RezerwacjeLib w nim znajdziemy plik App.config (należy filtr plików ustawić na wszystkie pliki *.*). Uzupełniamy kod jak w listingu 4.1. Listing 4.1: Program konsolowy operujący na upawnieniach 1 2 3 4 5 using using using using using System; System.Collections.Generic; System.Linq; System.Text; RezerwacjeLib; 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //Dodawanie ról/uprawnień Role role = new Role(); Uprawnienia upr; if ((upr = role.dodajUprawnienie("Inny", "Testowe uprawnienie bez ←znaczenia strategicznego")) != null) Console.WriteLine("ok dodano"); else return; upr = role.znajdzUprawnieniePoNazwie("Inny"); if (upr != null) 9 Rysunek 4.1: Dodawanie referencji 10 { 22 23 24 25 26 27 28 29 30 } } Console.WriteLine("ok znaleziono {0} a opis jest taki: {1} ",upr.←Nazwa, upr.Opis); upr.Nazwa = "Cocisk"; role.modyfikujUprawnienie(upr); } Console.ReadLine(); } W linii 5 oprócz standardowych przestrzeni nazw można dodać RezerwacjeLib. W linii 14 tworzymy obiekt klasy Role. W linii 15 deklarujemy obiekt upr klasy Uprawnienia zdefiniowanej jako klasa encji w pliku Rezerwacje.Designer.cs. Plik ten zosatł wygenerowany automatycznie, ale można go z ciekawości podejrzeć. Następnie wywołujemy metodę dodajUprawnienie sprawdzając czy dostaliśmy nowo stworzony obiekt. W linii 20 testujemy metodę szukającą, następnie znaleziony obiekt modyfikujemy a zmiany utrwalamy metodą modyfikujUprawnienie w lini 25. Gdy wszystko zadziałało możemy przejść do zakładki Server Explorer i podejrzeć dane w tabeli Uprawnienia klikając na jej nazwie prawym klawiszem myszy i wybierając z menu kontekstowego Show Table Data. Teraz możemy napisać kawałek programu, który doda faktyczne dane takie jak Administrator, Superuser, Wykładowca i Starosta. Należy pamiętać, że ewentualne zmiany w bibliotece pojawią się dopiero gdy ją przebudujemy (klawisz F6). Kilka ogólnych uwag: • Kody, które są tu umieszczone są wygenerowane do utworzonego wzorca bazy, który podawałem na poprzednich zajęciach. Jeżeli ktoś odszedł od podanego nazewnictwa otrzyma też inne klasy wygenerowane przez EF. Przykładowo: wielkość liter ma znaczenie idOsoby to nie to samo co IdOsoby o takie błędy łatwo, część z nich będzie na etapie kompilacji, jednak część z nich wyjdzie dopiero w momencie uruchomienia i np. odwołania się do bazy danych. • pamiętajmy by załączać wszystkie referencje i używać przestrzeni nazw podanych w instrukcji. • korzystajmy z Intellisense (inteligentego podpowiadacza) jeżeli edytor nie „widzi” lub „widzi” inaczej klasy lub składowe naszych klas to znaczy, że mogliśmy się pomylić w nazewnictwie lub przestrzeni nazw. • Ponieważ kod biblioteki „obrabiamy” w innej instancji Visual Studio a aplikację tworzymy w innej należy zwrócić uwagę w którym miejscu się znajdujemy. Szczególnie łatwo się pomylić gdy po uruchomieniu programu wystąpił błąd w bibliotece wtedy debuger otworzy nam odpowiedni plik *.cs w edytorze w którym uruchomiliśmy program. Nie należy go tam jednak edytować a jeśli już zapędziliśmy się i szkoda nam zmian należy wrócić do VS w którym edytujemy pliki biblioteki, zostaniemy poinformowani o tym że plik został zmieniony poza tym edytorem i czy załadować go ponownie, trzeba się zgodzić i kontynuować we właściwym miejscu. 11 • Jeżli wprowadzamy jakieś zmiany w pliku biblioteki np. Role.cs należy skompilować bibliotekę (F6) i dopiero wtedy przejść do kompilacji programu. • W przykładowych programach często brak jest przechwytywania wyjątków. Po pierwsze upraszczamy przedstawiony kod. Po drugie w razie awarii w trybie debugowania możemy sobie przejrzeć pełną informację. Oczywiście docelowo należy się zabezpieczyć na wszelkie ewentualności. • W przykładowych programach każda klasa powinna zaimplementować interfejs IDisposable by w przypadku usuwania zwolnić zapisać zmiany i zwolnić kontekst tak jak w przykładowym kodzie 4.2. • Podczas tworzenia nowych klas zwróćmy uwagę by były one dostępne, kwalifikator dostępu public. Domyślnie w C# klasy są prywatne. • Gdy kopiujemy kod z instrukcji wstawiają się numerki i usuwane jest formatowanie, trzeba też zwrócić uwagę na zawinięte linijki gdzie w miejscu złamania dorzucona została spacja i minus. Formatowanie można wykonać w następujący sposób. Zaznaczyć kod (ctrl+a) przejść w tryb edycji (ctrl+e) sformatować f. Tryb edycji pozwala też zakomentować zaznaczony tekst (literka c) lub odkomentować (literka u). Listing 4.2: Eleganckie kończenie 1 2 3 4 5 public void IDisposable() { context.SaveChanges(); context.Dispose(); } • Pamiętajmy, że po zmianach w bazie należy zaktualizować EDMX. 12 Rozdział 5 Rozszerzamy bibliotekę Teraz na podobnej zasadzie proszę napisać identyczną klasę obsługującą Tytuły. Nazwiemy ją Przedrostki. Powinna mieć analogiczne metody jak klasa zarządzająca uprawnieniami. Należy ją przetestować, sprawdzając wyniki w fizycznej bazie. Dodajmy od razu kilka tytułów takich jak: mgr, mgr inż., dr, dr inż., dr hab., dr hab. inż.,prof. itp. Możemy przejść do obsługi osób. Też należy stworzyć metody do dodawania, modyfikowania i wyszukiwania. Jednak tu musimy uwzględnić dodatkowe pola jak klucze obce. Chcielibyśmy też mieć możliwość szukania po nazwisku, imieniu, nazwisku i imieniu albo po fragmencie nazwiska. Tworzymy nową klasę Uzytkownicy.cs i w niej umieszczamy odpowiedni kod. W listingu 5.1 mamy metodę dodającą nową osobę. Listing 5.1: Metoda dodająca nową osobę 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public Osoby wstawOsobe(string imie, string nazwisko, string login, string haslo, ←string mail, string opis, Tytuly tytul, Uprawnienia uprawnienie) { Osoby os = new Osoby() { Imie = imie, Nazwisko = nazwisko, Login = login, Haslo =←haslo, Mail = mail, Opis = opis, IdTytulu = tytul.IdTytulu, IdUprawnienia = ←uprawnienie.IdUprawnienia }; context.Osoby.AddObject(os); try { context.SaveChanges(); } catch (Exception ex) { //nie udalo sie wstawic wiec usuwamy context.Osoby.DeleteObject(os); Console.WriteLine("Nie udało się dodać nowej osoby: " + ex.Message); Console.WriteLine(ex.InnerException.Message); return null; } Console.WriteLine("Dodano nową osobę: id={0} {1} {2}", os.IdOsoby, os.Imie, os.←Nazwisko); return os; } W listingu 5.2 mamy metodę aktualizującą daną osobę. Należy ją tak przerobić aby zwracała typ bool oznaczający czy metoda się powiodła. Oraz wypisywała dane diagnostyczne na konsolę. Analogicznie jak robi to metoda modyfikujUprawnienie. 13 Listing 5.2: Metoda modyfikująca wybraną osobę 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void aktualizujOsobe(Osoby osoba) { //najpierw szukamy odpowiedniej osoby po id EntityKey key = new EntityKey("rezerwacjeEntities.Osoby", "IdOsoby", osoba.←IdOsoby); Osoby os = (Osoby)context.GetObjectByKey(key); if (os != null) //jeżeli mamy ją to aktualizujemy { os.Imie = osoba.Imie; os.Nazwisko = osoba.Nazwisko; os.Login = osoba.Login; os.Haslo = osoba.Haslo; os.Mail = osoba.Mail; os.IdTytulu = osoba.IdTytulu; os.IdUprawnienia = osoba.IdUprawnienia; os.Opis = osoba.Opis; context.SaveChanges(); } } W kolejnej metodzie 5.3 widzimy przykład szukania z wykorzystaniem zapytań LINQ. Zwrotnie chcemy dostać listę osób, których nazwiska pasują do jednego ze wzorców (zaczyna się na, kończy na, zawiera, jest równe). Listing 5.3: Szukanie osób po nazwisku 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public List<Osoby> znajdzOsobyPoNazwisku(string nazwisko) { string bezGwiazdek = nazwisko.Replace("*", ""); if (nazwisko.StartsWith("*")) { if (nazwisko.EndsWith("*")) //jeżeli zaczyna się i kończy gwiazdką to znajdź ←zawierające return (from os in context.Osoby where os.Nazwisko.Contains(bezGwiazdek) ←select os).ToList(); //gdy tylko zaczyna się gwaizdką to znajdź kończące się nazwiska na podane ←nazwisko return (from os in context.Osoby where os.Nazwisko.Trim().EndsWith(←bezGwiazdek) select os).ToList(); } if (nazwisko.EndsWith("*")) //gdy tylko kończy się gwiazdka to tylko zaczynające ←się na dane litery { return (from os in context.Osoby where os.Nazwisko.StartsWith(bezGwiazdek) ←select os).ToList(); } //gdy brak gwiazdek chodzi nam o konkretne nazwisko return (from os in context.Osoby where os.Nazwisko == nazwisko select os).ToList←(); } Przetestujmy teraz nowe metody w programie konsolowym. Dodajemy następujący fragment kodu 5.4 w celu sprawdzenia czy da się dodać osoby. Listing 5.4: Testowanie dodawania osób 1 2 3 4 5 6 7 //Dodawanie uzytkownikow Uzytkownicy users = new Uzytkownicy(); Role rola = new Role(); try { users.wstawOsobe("Marian", "Wykładnik", "marianw", "maryjanek", "[email protected]"←, "testowo", Przedrostki.znajdzTytulPoNazwie("dr hab."),rola.←znajdzUprawnieniePoNazwie("Wykładowca")); // users.wstawOsobe("Anna", "Kajak", "azygzak", "azygzak1", "[email protected]", "←testowo", Tytuly.CreateTytuly(3), Uprawnienia.CreateUprawnienia(3, "tester")); 14 8 9 10 11 12 } catch (Exception ex) { Console.WriteLine("Blad wstawiania: " + ex.InnerException.Message); } Fragment kodu 5.5 pokazuje przykład użycia aktualizatora osób. Listing 5.5: Testowanie aktualizacji osób 1 2 3 4 5 6 7 8 //aktualizuj osobe Osoby os = users.znajdzOsobe(10L); Console.WriteLine("Znalaziono: " + os.Tytuly.Tytul + " " + os.Imie.Trim() + " " + os.←Nazwisko.Trim()); os.Opis = "to jest nowy opis po aktualizacji"; os.Imie = "Leonardo"; os.Login = "leon"; Console.WriteLine("login os [" + os.Login + "]"); users.aktualizujOsobe(os); We fragmencie kodu 5.6 mamy przykład jak wyszukać i wyświetlić wiele osób. Listing 5.6: Testowanie szukania osób 1 2 3 4 5 6 7 //szukanie osob i wyswietlanie ich List<Osoby> osoby = users.znajdzOsobyPoNazwisku("*"); Console.WriteLine("znaleziono nastepujace osoby w liczbie " + osoby.Count + " :"); foreach (Osoby it in osoby) { Console.WriteLine("-> " + (it.Tytuly != null ? it.Tytuly.Tytul + " " : "") + it.←Imie + " " + it.Nazwisko); } Kolejnym zadaniem jest napisanie analogicznej funkcji do szukania osób po imieniu a także po imieniu i nazwisku. Metoda szukająca po loginie też jest ciekawa ponieważ zapytanie LINQ może zwrócić całą kolekcję znalezionych obiektów a nam chodzi o jeden. Login jest jeden więc podajemy konkretny login i oczekujemy konkretnego użytkownika. Pomocna będzie metoda FirstOrDefault. Przkład jest w kodzie 5.7 Listing 5.7: Szukanie osób z danym loginem 1 2 3 4 5 public Osoby znajdzOsobePoLoginie(string login) { var osoby = (from os in context.Osoby where os.Login == login select os).←FirstOrDefault(); return osoby; } Jednym z ostatnich samodzielnych zadań na dziś będzie dodanie obsługi zasobów. Jednak należy zostawić to już na sam koniec, gdy zostanie chwila czasu. Na chwilę obecną należy dodać ręcznie ze 2-3 zasoby za pomocą Server Explorera aby można było przetestować dalsze modyfikacje. 15 Rozdział 6 Dodawanie rezerwacji Dodanie rezerwacji wiąże się z rozwiązaniem kilku problemów. Po pierwsze trzeba sprawdzić czy w danym przedziale czasowym nie uczestniczy już zasób, który jest na liście rezerwowanych aktualnie zasobów. Należy też uwzględnić, przypadek spotkań kiedy to rezerwujemy komuś czas i także sprawdzić jego zajętość w podanym terminie. Po drugie powinniśmy rozwiązać problem współbieżności. Można to zrobić np. tworząc procedurę składowaną, którą wykonywalibyśmy w transakcji. Aby użyć klasy TransactionScope należy dodać do referencji System.Transactions. My jednak posługujemy się EF i za jego pomocą postaramy się rozwiązać ten problem. W normalnym scenariuszu odczytalibyśmy zawartość tabelek, sprawdzili warunki oraz dopisali lub zmienili zawartość tabelek. W kodzie przedstawiony w listingu 6.1 przed wywołaniem SaveChanges można dodać sztuczne zatrzymanie programu np. za pomocą Console.ReadLine. Uruchomić dwa programy z identycznymi parametrami rezerwacji i nacisnąć enter w jednym a potem w drugim. Teraz to samo powtórzyć ale komentując kod odpowiedzialny za transakcje. Listing 6.1: Dodawanie rezerwacji 1 2 3 4 public void dodajRezerwacje(long idDodajacego, DateTime dtStart, DateTime dtStop, ←List<long> idkiZasobow, List<long> idkiLudzikow, TypyRezerwacji typ) { using (TransactionScope scope = new TransactionScope()) { 5 6 7 8 9 10 11 12 13 14 15 16 17 //szukamy konfliktowych rezerwacji, czyli takich gdzie ktorys z zasobow (lub ←osob) jest zajety w podanych przedzialach czasowych var konfliktoweRez = (from rz in context.Rezerwacja from zas in context.RezerwacjaZasoby from zasobyDoRezerwacji in idkiZasobow from os in context.RezerwacjaOsoby from osobyDoRezerwacji in idkiLudzikow where ((rz.Od <= dtStart && rz.Do > dtStart) || (←dtStart <= rz.Od && dtStop > rz.Od)) //warunek czy podany przedzial czasowy nie←zalapuje innych rezerwacji z tymi samymi zasobami && rz.IdRezerwacji == zas.IdRezerwacji //←zlaczenie tabeli rezerwacji z tabela zawierajaca identyfikatory zasobow && zas.IdZasobu == zasobyDoRezerwacji //←uwzglednienie tylko zasobow ktore chcemy zarezerwowac && os.IdRezerwacji == rz.IdRezerwacji //i czy ←dane osoby tez nie sa zajete w tym czasie && os.IdOsoby == osobyDoRezerwacji select rz).Distinct(); //Distinct by zwracalo unikalne ←rekordy rezerwacji (bo gdy rezerwacja ma np. dwa zasoby to wtedy zwracana jest ←- 16 dwa razy) 18 19 20 21 22 23 24 25 26 27 if (konfliktoweRez.Count() > 0) //jezeli sa jakies konflikty to wypisz i ←wyjdz. Moze pozwolimy jakos inaczej reagowac { Console.WriteLine("Znalazl konfliktowe rezerwacje w liczbe: " + ←konfliktoweRez.Count()); foreach (Rezerwacja r in konfliktoweRez) { Console.WriteLine("Rezerwacja {0} od: {1} do: {2}", r.IdRezerwacji, r←.Od, r.Do); } return; } 28 29 30 31 32 //zakladamy propozycje Propozycje propozycja = new Propozycje(); propozycja.IdOsoby = idDodajacego; //kto składa propozycje context.AddToPropozycje(propozycja); 33 34 35 36 37 38 39 //dodajem rezerwacje okreslajac date i typ Rezerwacja rezerwacja = new Rezerwacja(); rezerwacja.Od = dtStart;// DateTime.Parse(dataOdstr); //od kiedy rezerwacja.Do = dtStop; // DateTime.Parse(dataDostr); //do kiedy rezerwacja.IdTypuRez = typ.IdTypuRez; //jakiego typu rezerwacja context.AddToRezerwacja(rezerwacja); 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 //wstawiamy laczniki do rezerwowanych zasobow przez dana rezerwacje foreach (long idz in idkiZasobow) { RezerwacjaZasoby rezZas = new RezerwacjaZasoby(); rezZas.IdRezerwacji = rezerwacja.IdRezerwacji; rezZas.IdZasobu = idz; // tutaj co rezerwujemy context.AddToRezerwacjaZasoby(rezZas); } bool trzebaZgody = false; //Rezerwujemy czas większej liczbie osób np. chcemy zorganizowac spotkanie foreach (long ido in idkiLudzikow) { RezerwacjaOsoby rezOs = new RezerwacjaOsoby(); rezOs.IdOsoby = ido; rezOs.IdRezerwacji = rezerwacja.IdRezerwacji; if (rezOs.IdOsoby == idDodajacego) //jeżeli dodajemy sami siebie to z ←automatu jest zgoda rezOs.Zgoda = true; else { rezOs.Zgoda = false; trzebaZgody = true; } context.AddToRezerwacjaOsoby(rezOs); } if (!trzebaZgody) //jeśli ja zakładam to nie trzeba zgody i można uaktywnić { rezerwacja.StanyRezerwacji = context.StanyRezerwacji.Where("it.Stan='←Aktywna'").FirstOrDefault(); //1 Aktywna } else //jesli sa inne osoby trzeba poczekac na ich zgode { rezerwacja.StanyRezerwacji = context.StanyRezerwacji.Where("it.Stan='Do ←zatwierdzenia'").FirstOrDefault(); // 4 do zatwierdzenia } //Console.WriteLine("Czekamy przed zapisem do bazy, klepnij enter"); //Console.ReadLine(); try { context.SaveChanges(); //ostatecznie zapisujemy zmiany, EF robi to w ←transakcji. } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.InnerException.Message); return; 17 84 85 86 87 } } } scope.Complete();//jezeli wszystko ok to zatwierdzamy transakcje Do klasy Rezerwacje należy jeszcze dodać metodę znajdzTypRezerwacji gdzie parametrem wejściowym będzie typ w postaci stringa. Kod 6.2 przedstawia przykład użycia metody dodającej rezerwację. Listing 6.2: Dodawanie rezerwacji przykład 1 2 3 4 5 6 7 Rezerwacje rez = new Rezerwacje(); //dodawanie rezerwacji DateTime dtStart = DateTime.Parse("2013-04-20 20:00:00"); DateTime dtStop = DateTime.Parse("2013-04-20 21:00:00"); List<long> idZasobow = new List<long> { 3 }; List<long> idOsob = new List<long> { 5 }; rez.dodajRezerwacje(5L,dtStart,dtStop,idZasobow,idOsob,rez.znajdzRezerwacje("Egzamin←")); Jeżeli data dodania automatycznie nam się nie wypełnia tylko ustawiana jest np na 1900.01.01 należy napisać triggera wstawiającego bieżącą datę. Spowodowane jest to tym, że obiekty posiadające pola typu datetime automatycznie są inicjowane a co za tym idzie INSERT do bazy ma podane pola z datą, więc domyślna wartość getDate nie zadziała. W przypadku gdy chcemy usunąć rezerwacje należy pamiętać, że mamy relacje pomiędzy tabelami. Usuwając rekord z Rezerwacji trzeba też usunąć wszystko co było podłączone. Najwygodniej jest ustawić sobie usuwanie kaskadowe na relacjach pomiędzy tabelami. Wchodzimy w edycję tabeli Rezerwacje wybieramy z menu kontekstowego Relationship i tam odszukujemy interesujące nas relacje. Rozwijając INSERT And UPDATE Specyfication ustawiamy Delete Rule na Cascade (rys. 6.1). Rysunek 6.1: Dodawanie referencji 18 Rozdział 7 Ocena Za dzisiejsze laboratoria przewidywana jest następująca punktacja: • Odtworzenie biblioteki z Rolami oraz poprawne użycie ich w programie konsolowy 1 pkt. Wcześniej oczywiście należy założyć odpowiednie projekty i je skonfigurować przez dodanie referencji wygenerowanie edmx itd. • Stworzenie własnej klasy Przedrostki z metodami które dodają, szukają oraz modyfikują. Oczywiście nową klasę trzeba przetestować i zademonstrować w aplikacji konsolowej 1 pkt. • Odtworzenie klasy Uzytkownicy oraz poprawne użycie w aplikacji konsolowej 0,5 pkt. • Stworzenie własnej metody szukajPoImieniu wraz z przetestowaniem w aplikacji 0,2 pkt. • Stworzenie własnej metody szukajPoImieniuINazwisku wraz z przetestowaniem w aplikacji 0,3 pkt. • Odtworzenie klasy Rezerwacje 0,2 pkt. • Dodanie własnej metody znajdzTypRezerwacji 0,3 pkt. • Dodanie triggera ustawiającego datę dodania 0,5 pkt. • Dodanie obsługi „zasobów” analogicznie jak w klasie Role i Przedrostki 1 pkt. Przez odtworzenie rozumiem przepisanie kodu z instrukcji, stworzenie czy dodanie jest pracą wykonaną samodzielnie jedynie na podstawie instrukcji. Pamiętajmy o terminowym wysłaniu sprawozdania, to też jest szansa na 1 punkt. 19 Bibliografia [1] A. Troelsen Język C# 2010 i platforma .NET 4.0, PWN 2011 1 20