WEiI PK Katedra Inżynierii Komputerowej Badanie technologii LINQ Ćwiczenia z przedmiotu Obiektowe bazy danych Włodzimierz Khadzhynov 2013-03-02
Spis treści Wstęp... 4 LINQ to Objects... 5 Rozszerzenia języka C#3.0 dla LINQ... 5 Wykorzystanie metod anonimowych... 7 Zmienna Var... 8 Typy anonimowe... 8 Wykorzystanie lambda-wyrażeń... 9 Operatory zapytań... 10 Wyrażenia zapytaniowe (query expression)... 10 Wykorzystanie metody Cast lub OfType... 10 LINQ to XML... 12 LINQ to SQL... 13 Generacja klas encyjnych dla LINQ to SQL.... 14 Tworzenie klas encyjnych za dopomogą utility SqlMetal.... 14 Tworzenie klas encyjnych za dopomogą Object Relational Designer.... 14 Modyfikacja kolumny w bazie... 18 Wstawianie danych.... 19 Pobieranie baz Northwind oraz Pub oraz dołączenie tych baz do MS SQL Server 2008.... 21 LINQ to DataSet... 22 Zadania ćwiczeniowe dla studenta... 23 1.LINQ to Object... 23 2.LINQ to XML... 23 3.LINQ to SQL z wykorzystaniem SQLMETAL... 23 4. LINQ to SQL z wykorzystaniem O/R Designera... 23 5. Projektowanie zapytań LINQ... 23 2
Select... 24 Ćwiczenie dla studentów... 27 Where... 27 Ćwiczenie dla studentów... 30 OrderBy... 30 Ćwiczenie dla studentów... 33 Take i Skip... 33 Ćwiczenie dla studentów... 34 GroupBy... 35 Ćwiczenie dla studentów... 39 Distinct, Union, Intersect, Except... 39 Ćwiczenie dla studentów... 43 Join Operators... 43 Ćwiczenie dla studentów... 45 3
Wstęp LINQ (Language Integrated Query zapytania zintegrowane) to technologia zapytań zintegrowanych z językiem programowania, pozwalająca na pisanie zapytań dotyczących lokalnych kolekcji obiektów i zdalnych źródeł danych, a przy tym podlegających kontroli typów. Podstawowymi jednostkami danych w LINQ są kolekcje i elementy. Kolekcja danych odpytywana przez LINQ musi implementować interfejs IEnumerable<>, a element to dowolny pojedynczy element takiej kolekcji. Dostęp do obiektów wewnątrz programu oraz do obiektów baz danych jest zrealizowany w sposób jednolity. LINQ to składnia, którą można wykorzystać do odpytywania różnych źródeł danych: obiektów, baz danych SQL, dokumentów XML, encji itd. LINQ posiada pełne wsparcie dla transakcji, widoków, procedur przechowanych. Zapytania LINQ stają się częścią języka wspierającego.net (C#, VB itd.).wszystkie operatory zapytań są implementowane jako statyczne metody rozszerzające klasy Enumerable. Jeśli LINQ ma działać, musi znać mapę całej bazy danych. Zapytanie LINQ zwraca kolekcję z przestrzeni nazw typów ogólnych. Kolekcja ta może być modyfikowana, a następnie zwrócona do źródła. Dzięki temu zachowywana jest pełna kontrola typów danych i ich konwersji w poszczególnych mechanizmach pośredniczących w pobieraniu danych. Dane mogą pochodzić z: zwykłych obiektów (LINQ to objects), bazy danych (LINQ to sql), dokumentów XML (LINQ to XML). Można zastosować na nich takie operacje jak: projekcje lub złączenia. LINQ zapożyczył z języka SQL sposób budowania zapytań. Każdy programista mający kontakt z językiem SQL nie będzie miał większych problemów z tworzeniem zapytań LINQ. Na rys.1 pokazany jest schemat architektury LINQ. Rys.1 LINQ to Objects to jest interfejs API IEnumerable <T> dla standardowych operacji zapytań. LINQ to Objects pozwala realizować zapytania do tablic i kolekcji danych. Standardowe operacji zapytań są w postaci metod statycznych klasy System.Linq.Enumerable. 4
LINQ to Objects Przykład zapytania LINQ do tablicy ciągów znaków jest pokazany w listingu 1. Listing 1. 1. using System; 2. using System.Linq; 3. string[] greetings = "hello world", "hello LINQ", "hello Politechnika" ; 4. var items = from s in greetings where s.endswith("linq") select s; 5. foreach (var item in items) 6. Console.WriteLine(item); 7. Console.ReadLine(); Z tego listingu można zrozumieć, że LINQ potrzebuje bibliotekę System.Linq. Zapytanie do danych podobno jest formatu zapytania SQL, ale z odwrotnym porządkiem operatorów oraz zawiera konstrukcje s in w operatorze from. Oprócz tego każdy obiekt zawiera sporo dodatkowych metod. Zwłaszcza, obiekt s zawiera metodę EndsWith(). Główną zaletą LINQ jest pełna integracja z językiem C#. LINQ nie proponuje nowe klasy dla realizacji swoich zapytań. LINQ może pracować z istniejącymi kolekcjami oraz tablicami, którzy są skojarzone z istniejącymi klasami. Głównymi wymogami do tych kolekcji jest konieczność realizacji interfejsu IEnumerable <T>. Aby ułatwić odczytywanie danych z nieznanych źródeł, w LINQ wprowadzono zmienne deklarowane przy użyciu słowa kluczowego VAR (zmienne typu anonimowego). Typ zmiennych tego rodzaju jest ustalany przez kompilator na podstawie wartości użytej przy inicjalizacji. Przy tym inicjalizacja musi nastąpić w tej samej linii kodu co deklaracja. Po zainicjowaniu zmienne z typem VAR respektują kontrolę typów, to znaczy, np. do zmiennej typu VAR zainicjowanej stałą typu INT nie można już przypisać łańcucha znaków. Zmienne tego typu mogą być jedynie zmiennymi lokalnymi, a więc mogą być deklarowane i inicjowane tylko wewnątrz metody. Przykład nieprawidłowego wykorzystania VAR jest pokazany w listingu: var name = "Joe"; name = 1; Rozszerzenia języka C#3.0 dla LINQ Metody rozszerzające to są metody którzy są statycznymi oraz mogą być wywołane na instancji (obiekcie) klasy przez notację punktową bez konieczności odwołania do klasy. Rozszerzenia (extension methods) pozwalają dodawać nowe metody do istniejących klas bez modyfikacji ich kodu. Metody rozszerzające muszą być definiowane jako statyczne w statycznych klasach niezagnieżdżonych oraz mogą być wywoływane tak, jak metody składowe innych wcześniej zdefiniowanych klas. Dla przykładu dodajmy do klasy String metodę zmieniającą apostrof w hash. W listingu 2 jest pokazany ten przykład. Listing 2. 1. using System; 2. using System.Collections; 3. using System.Collections.Generic; 4. using System.Diagnostics; using System.Linq; 5. using System.Text; 6. static void Test_rozszerzenia() 5
7. 8. Console.WriteLine("0 : Begin", new StackTrace(0, 9. true).getframe(0).getmethod().name); 10. string s = "SQL Inje'c'tion"; 11. Console.WriteLine(s.UsunApostrof()); 12. Console.WriteLine(s.UsunApostrof('#')); 13. Console.WriteLine(s.UsunApostrof(c => (c == '\'')? '#' : c)); 14. Console.WriteLine("0 : ", new StackTrace(0, 15. true).getframe(0).getmethod().name); 16. Console.ReadLine(); static class Rozszerzenia 17. 18. public static string UsunApostrof(this String argument) 19. 20. return argument.replace('\'', '#'); 21. 22. public static string UsunApostrof(this String argument, char 23. zamiennik) 24. 25. return argument.replace('\'', zamiennik); 26. 27. delegate char DZmieniacz(char znak); 28. //public static string UsunApostrof(this String argument, 29. DZmieniacz zmieniacz) 30. public static string UsunApostrof(this String argument, 31. Func<char, char> zmieniacz) 32. 33. string wynik = ""; 34. foreach (char znak in argument.tochararray()) 35. wynik += zmieniacz(znak); 36. return wynik; 37. 38. W liniach 7-17 jest wyznaczona metoda Test_rozszerzenia() która wykorzysta rozszerzenia dla klasy string definiowane w liniach 18-38. Niezbędnym dla rozszerzenia klasy String w tym przykładzie jest obecność argumentów This String (obecny obiekt) w metodach rozrzedzających. Argument This String musi być zawsze pierwszym argumentem w metodzie rozrzedzającej. Przy prawidłowej definicji metody jest ona widoczna w IntelliSense dowolnego obiektu klasy String. W liniach 19-35 są definiowane trzy różne stereotypy metod rozszerzających UsunApostrof()dla klasy string. W liniach 28-35 jest definiowana rozszerzająca z wykorzystaniem delegata funkcji jako argumentu. Delegat został deklarowany w linii 28 oraz metoda rozszerzająca w linii 31: public static string UsunApostrof(this String argument, Func<char, char> zmieniacz) Tu jest wykorzystany typ parametryczny Func<char, char>. Jego pierwszym parametrem może być typ argumentu (char), a drugim typ zwracany przez metodę (char). W linii 14 pokazany przykład wykorzystania tej metody za dopomogą wyrażenia Lambda: Console.WriteLine(s.UsunApostrof(c => (c == '\'')? '#' : c)); Technologia LINQ oprócz możliwości integracji zapytań pozwoli realizować sporo funkcji integracji danych w programowaniu obiektowym. Rozpatrzymy przykład. Przypuścimy że mamy dowolną metodę A która wraca tablicę ciągów znaków typu STRING. Ten wynik trzeba przekazać do metody B, która potrzebuje na wejściu ciąg znaków typu INTEGER. W zwykłych przypadkach dla przekazania danych do metody B są potrzebna dodatkowa pętla do przekształcenia tablicy STRING do postaci 6
typu INTEGER. W przypadku wykorzystania LINQ ta operacja może być zrealizowana przez lambda wyrażenia oraz wewnętrzne metody zapytań w sposób podobny w listingu 3. Listing 3. 1. using System; 2. using System.Collections; 3. using System.Collections.Generic; 4. using System.Diagnostics; 5. using System.Linq; 6. using System.Text; 7. string[] numbers = "0042", "010", "9", "27" ; 8. int[] nums = numbers.select(s => Int32.Parse(s)).ToArray(); 9. foreach (int num in nums) 10. Console.WriteLine(num); 11. Console.ReadLine(); W linii 7 jest zdefiniowana tablica ciągów znaków. W linii 8 jest zrealizowane przekształcenie tablicy numbers do nowej tablicy nums. Dla realizacji dodatkowej operacji sortowania w poprzednim przykładzie może być zrealizowany następny kod (Listing 4). Listing 4. 1. using System; 2. using System.Collections; 3. using System.Collections.Generic; 4. using System.Diagnostics; 5. using System.Linq; 6. using System.Text; 7. string[] numbers = "0042", "010", "9", "27" ; 8. int[] nums = numbers.select(s => Int32.Parse(s)).OrderBy(s => 9. s).toarray(); 10. foreach (int num in nums) 11. Console.WriteLine(num); 12. Console.ReadLine(); Wykorzystanie metod anonimowych Wykorzystanie delegatów potrzebuje w klasycznym podejściu definicję delegata oraz następną instancję tego delegata do konkretnej metody. W przypadkach kiedy metody delegata będą wykorzystane tylko jeden raz lepiej tworzyć metody anonimowe. Metody anonimowe pozwalają specyfikować kody metod w punkcie przekazywania delegata. Aby napisać metodę anonimową, należy zastosować słowo kluczowe delegate uzupełnione o deklarację parametrów, a następnie właściwe ciało funkcji. W listingu 5. Jest pokazany kod programu dla filtracji tablicy liczb typu Integer. Listing 5. 1. using System; 2. using System.Collections; 3. using System.Collections.Generic; 4. using System.Diagnostics; 5. using System.Linq; 6. using System.Text; 7
7. int[] nums = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; 8. int[] oddnums = Common.FilterArrayOfInts(nums, delegate(int i) return ((i & 1) == 1); ); 9. foreach (int i in oddnums) 10. Console.WriteLine(i); 11. Console.ReadLine(); 12. public class Common 13. 14. public delegate bool IntFilter(int i); 15. public static int[] FilterArrayOfInts(int[] ints, 16. IntFilter filter) 17. 18. ArrayList alist = new ArrayList(); 19. foreach (int i in ints) 20. 21. if (filter(i)) 22. 23. alist.add(i); 24. 25. 26. return ((int[])alist.toarray(typeof(int))); 27. 28. W liniach 12-28 jest definiowana klasa Comon. W linii 14 jest definiowany delegat: public delegate bool IntFilter(int i), który jest wykorzystany w metodzie FilterArrayOfInts(int[] ints,intfilter filter.instancja delegata jest zrealizowana w linii 8. Zmienna Var Wykorzystanie zmiennej o słowie kluczowym var daje możliwość określania typu zmiennej lokalnej przy inicjalizacji. Typ zmiennej w trym przypadku jest ustalany przez kompilator na podstawie wartości użytej w moment inicjalizacji, np.: var i = 7; --> int i =7; var lancuch = "Slowo"; --> string lancuch = "Slowo"; var liczby = new int [ ] 1,2,3; --> int[ ] liczby = new int[ ] 1,2,3; Inicjalizacja zmiennej następuje w tej samej linii kodu co deklaracja. Dzięki zastosowaniu słowa kluczowego var można odczytywać dane z nieznanych źródeł. Jest to bardzo przydatne przy stosowaniu zapytań LINQ. Typy anonimowe Typ anonimowy to prosta klasa utworzona w locie do przechowywania zestawu wartości. Aby utworzyć typ anonimowy, należy zastosować słowo kluczowe New z inicjalizatorem obiektu zawierającym nazwy i wartości dla poszczególnych właściwości, które mają zostać zawarte w typie anonimowym: var anonim=new i=1, l=1l, s="politechnika", f=1.0f, d=1.0; MessageBox.Show(anonim.GetType().FullName); MessageBox.Show(anonim.s); 8
Wykorzystanie lambda-wyrażeń Lambda wyrażenia są skróconą postacią do specyfikacji algorytmu. Lambda wyrażenia są specyfikowane jako lista parametrów, operacją lambda => oraz listę operacji: (parameter 1, parameter 2,,parameter N) => Operator 1; Operator 2; Operator N; Return (typ zwracanego lambda wyrażenia); Przykłady : x => x ; To wyrażenie oznaczy, że x wraca x. x=>x.length>0; To wyrażenie oznaczy, że x wraca wartość boolowską. x=>x.length ; To wyrażenie oznaczy, że x wraca wartość typu Integer. (x,y) => x==y; To wyrażenie oznaczy, że (x,y) wracają wartość boolowską. (x,y) => If (x>y) Return(x); Else Return(y); 9
Lambda wyrażenia można wykorzystać do wyspecyfikowania delegatów w programie. Ten przykład jest pokazany w listingu 6. W listingu 6 jest wykorzystany poprzedni przykład filtracji liczb, dlatego zamienione zostali tylko linii 7-11 kodu listingu 6. Listing 6 int[] nums = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; int[] oddnums = Common.FilterArrayOfInts(nums, i => ((i & 1) == 1)); foreach (int i in oddnums) Console.WriteLine(i); Operatory zapytań Metoda przekształcająca sekwencję wejściową nazywa się operatorem zapytania klasie Enumerable zdefiniowano około 40 operatorów zapytań oraz wszystkie są jako statyczne metody rozszerzające. Te operatory nazywają standardowymi operatorami zapytań. Kaskadowe operatory zapytań można budować z pojedynczych operatorów zapytań. Wyrażenia zapytaniowe (query expression) Wyrażenie zapytaniowe zawsze zaczyna się od klauzuli from i zawsze kończy się klauzulą select,albo group. Klauzula from deklaruje zmienną zakresową, która można traktować jako zmienną przemierzającą elementy kolekcji wejściowej ( podobne jak w instrukcji foreach). Kompilator przetwarza wyrażenia zapytaniowe, tłumacząc je na operatory kaskadowe. Główną zaletą składni zapytań jest to, że upraszcza ona zapytania, w których występują : Klauzula let do wprowadzenia nowej zmiennej obok zmiennej zakresowej, Generatory (SelectMany) z odwołaniem do zewnętrznej zmiennej zakresowej, Odpowiednik Join albo GroupJoin z odwołaniem do zewnętrznej zmiennej zakresowej. Wykorzystanie metody Cast lub OfType Operacji zapytań LINQ mogą być wywoływane na kolekcjach którzy realizują interfejs IEnumerable< T>. Żadna z dziedziczonych kolekcji C# z przestrzeni System.Collection nie realizuje IEnumerable< T>. Wykorzystać kolekcje dziedziczone można przez zapytania Cast<> dla konwersji do sekwencji IEnumerable<T >. Listing 1.9 Listing 1.6 przykład przekształcenia sekwencji klasy Employee do sekwencji innej klasy Contact. Klasa Employee przeznaczona jest dla magazynowania danych pracowników w postaci obiektów klasy ArrayList. Klasa ArrayList reprezentuje tablicę obiektów klasy obiect o dynamicznym rozmiarze. Klasa Employee zawiera metodę GetEmployees (), która wraca wszystkich pracowników jako sekwencję obiektów ArrayList. Metoda PublishContacts(Contact[] contacts) publikuje kontakty oraz potrzebuje na wejściu sekwencje obiektów typu Contact[]. Dla publikacji kontaktów wszystkich pracowników 10
trzeba przekształcić sekwencje obiektów klasy Employee typu ArrayList do sekwencji obiektów typu Contact[]. Standardowa metoda Cast() realizuje konwersję ArrayList obiektów Employee do sekwencji IEnumerable< Employee>. W metodzie Select() jest stworzona sekwencja obiektów IEnumerable< > z niezbędnymi polami klasy Contact. Metoda ToArray() konwertuje IEnumerable< > do sekwencji Contact[]. static void Listing1_6() Console.WriteLine("0 : Begin", new StackTrace(0, true).getframe(0).getmethod().name); ArrayList alemployees = LINQDev.HR.Employee.GetEmployees(); LINQDev.Common.Contact[] contacts = alemployees.cast<linqdev.hr.employee>().select(e => new LINQDev.Common.Contact Id = e.id, Name = string.format("0 1", e.firstname, e.lastname) ).ToArray<LINQDev.Common.Contact>(); LINQDev.Common.Contact.PublishContacts(contacts); Console.WriteLine(alEmployees.GetType()); Console.WriteLine("0 : End", new StackTrace(0, true).getframe(0).getmethod().name); Console.ReadLine(); namespace LINQDev.HR public class Employee public int id; public string firstname; public string lastname; public static ArrayList GetEmployees() //Oczywiście prawdopodobnie tutaj będzie prawdziwy kod zapytania do bazy danych ArrayList al = new ArrayList(); al.add(new Employee id = 1, firstname = "Piotr", lastname = "Osowski" ); al.add(new Employee id = 2, firstname = "Bartosz", lastname = "Ciwinski" ); al.add(new Employee id = 3, firstname = "Andrzej", lastname = "Smoliak" ); return (al); namespace LINQDev.Common public class Contact public int Id; public string Name; 11
public static void PublishContacts(Contact[] contacts) // This publish method just writes them to the console window. foreach (Contact c in contacts) Console.WriteLine("Contact Id: 0 Contact: 1", c.id, c.name); LINQ to XML LINQ to XML to jest interfejs LINQ API przeznaczony dla XML. Klasy i metody są rozlokowane w System.Xml.Linq. Przykład zapytania do XML z wykorzystaniem LINQ to XML pokazany jest w listingu 7. Listing 7. //Listing1_2(); using System; using System.Linq; using System.Xml.Linq; XElement books = XElement.Parse( @"<books> <book> <title>bazy danych</title> <author>włodzimierz Khadzhynov</author> </book> <book> <title>pro WF: Windows Workflow in.net 3.0</title> <author>bruce Bukovics</author> </book> <book> <title>pro C# 2005 and the.net 2.0 Platform, Third Edition </title> <author>andrew Troelsen</author> </book> </books>"); var titles = from book in books.elements("book") where (string)book.element("author") == "Włodzimierz Khadzhynov" select book.element("title"); foreach (var title in titles) Console.WriteLine(title.Value); Console.ReadLine(); W tym listingu plik XML jest załadowany do kolekcji obiektów books klasy XElement za dopomogą metody Parse. LINQ to XML pozwoli nie wykorzystać klasę XmlDocument. 12
LINQ to SQL. LINQ to SQL pojawił się wraz z Visual studio 2008 i jest to technologia mapująca bazę danych SQL Server do postaci obiektowej, czyli O/RM (object relational mapping) oraz pozwalająca na odpytywanie bazy danych za pomocą składni LINQ. Za pomocą LINQ to SQL w bardzo łatwy sposób można wykonać CRUD (create, read, update, delete) na bazie danych. Na rys.2 jest pokazane w jaki sposób są przekształcone zapytania LINQ na poziomie aplikacji do zapytań serwera SQL. Rys.2 W listingu 6 jest pokazany przykład wykorzystania zapytania LINQ to SQL. Listing 8 1. using System.Linq; 2. using System.Data.Linq; 3. using nwind; //Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"); 4. Northwind db = new Northwind(@"server=localhost;uid=sa;pwd=asdf;database=Northwind"); 5. var custs = from c in db.customers where c.city == "London" select c; 6. foreach (var cust in custs) 7. Console.WriteLine("0", cust.companyname); 8. Console.ReadLine(); Tu została dodana w trzeciej linii kodu przestrzeń nazw nwind (linia 3). Ta przestrzeń nazw została zdefiniowana w pliku Nortwind.cs oraz zawiera klasę Northwind (patrz rys. 3). Klasa Northwind dziedziczy klasę DataContext oraz prezentuje całą bazę danych w postaci zbioru klas encyjnych niezbędnych do odwołania do obiektów tabeli bazy danych. 13
Rys.3. Generacja klas encyjnych dla LINQ to SQL. Plik Northwind.cs oraz przestrzeń nwind która zawiera klasę DataContext, mogą być stworzone w dwa sposoby: za dopomogą utility SQLMetal lub przez Object Relational Designer. Tworzenie klas encyjnych za dopomogą utility SqlMetal. Utilita SQLMetal jest w składzie DOTNET Framework 3.5 oraz może być wykorzystana tylko dla baz danych MS SQL Server. Przykład uruchomiania SQLMetal jest pokazany na rys.4. Rys.4 Po zakończeniu programu sqlmetal plik northwind.cs trzeba dodać do projektu. Tworzenie klas encyjnych za dopomogą Object Relational Designer. Designer pozwoli tworzyć klasy encyjne przez przeciąganie tablic bazy danych w pole projektu. Dla tworzenia tych klas są potrzebne następne kroki: 14
1. Dodać do projektu plik klas LINQ to SQL. Nazwa tego pliku może być dowolną, ale lepiej skojarzyć je z nazwą bazy danych. Po kliknięciu ADD pojawi się puste okno Designera: 2. W oknie Server Explorer trzeba ustalić połączenie z bazą danych: 15
3. Trzeba przenieść obiekty bazy danych do pola designera: 4. Prawy panel designera(na rys. niżej) służy dla tworzenia metod kontekstu. Na ten panel można przeciągnąć przechowywane procedury bazy danych: 16
Rezultaty tworzenia klas encyjnych będą w pliku mojabaza.dbml oraz w następnych plikach : Informacja o bazie danych klasy DataContext będzie wyznaczona w pliku app.config: Po stworzeniu pliku mojabaza.dbml są stworzone wszystkie klasy tablic bazy danych. To można spojrzeć w oknie browsera obiektów: 17
Modyfikacja kolumny w bazie W listingu 9 jest pokazany przykład modyfikacji kolumny w tablice Customer bazy danych Northwind. W linii 18 został stworzony obiekt db klasy DataContext. Głównym przeznaczeniem klasy DataContext jest połączenie z bazą danych. Ta klasa zawiera serwisy dla sprawdzenia dokonanych zmian w bazie danych oraz dla realizacji tych zmian. Oddzielne tablice w bazie danych są właściwościami klasy DataContext, dlatego odwołać się do obiektów reprezentujących tablicę można w sposób pokazany w linii 19: db.customers. Listing 9. 1. using System; 2. using System.Collections.Generic; 3. using System.Data.Linq; 4. using System.Diagnostics; // For the StackTrace. 5. using System.Linq; 6. using System.Text; 7. using nwind; 8. namespace LINQChapter12 9. 10. class Program 11. 12. static void Main(string[] args) 13. Listing12_1(); 14. 15. static void Listing12_1() 16. 17. Console.WriteLine("0 : Begin", new StackTrace(0, true).getframe(0).getmethod().name); // tworzenie obiektu DataContext. 18
Northwind db = new Northwind(@"server=localhost;uid=sa;pwd=asdf;database=Northwind"); // Wywołanie użytkownika LAZYK. 18. Customer cust = (from c in db.customers 19. where c.customerid == "LAZYK" 20. select c).single<customer>(); 21. Console.WriteLine(cust.ContactName); // modyfikacja pola contact name. 22. cust.contactname = "Ned Plimpton"; 23. try 24. // zachowanie zmian. 25. db.submitchanges(); 26. // Detect concurrency conflicts. 27. catch (ChangeConflictException) 28. // Resolve conflicts. 29. db.changeconflicts.resolveall(refreshmode.keepchanges); 30. 31. Console.WriteLine("0 : End", new StackTrace(0, true).getframe(0).getmethod().name); 32. Console.ReadLine(); 33. 34. 35. Wykorzystanie LINQ to SQL dla baz danych można rozpatrywać dla następnych typów operacji z rekordami: Wstawiania Zapytania Modyfikacji Usuwania. Wstawianie danych. Obiekt DataContext odwzorowuje dowolne asocjacje Stworzenie egzemplarzy klasy encyjnej nie jest jeszcze wystarczającym dla ostatecznego wstawiania rekordu do bazy danych. Obiekt encyjny musi być wstawiany do kolekcji tablicy Table<T> (gdzie T typ klasy encyjnej) wszystkich kolekcji tablic kontekstu, lub musi być dodany do kolekcji EntitySet<T> odpowiedniego obiektu encyjnego, zawierającego inne obiekty. Przykład realizacji operacji wstawiania rekordów danych ze wstawianiem do kolekcji tablicy encji bazy danych jest pokazany w listingu 10. Listing 10. 1. using System; 2. using System.Collections.Generic; 3. using System.Data.Linq; 4. using System.Diagnostics; 5. using System.Linq; 6. using System.Text; 7. using nwind; 8. Console.WriteLine("0 : Begin", new StackTrace(0, true).getframe(0).getmethod().name); // 1. Tworzenie the DataContext. 19
//Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"); 9. Northwind db = new Northwind(@"server=localhost;uid=sa;pwd=asdf;database=Northwind"); 10. // 2. Tworzenie instancji obiektu encyjnego 11. Customer cust = new Customer 12. 13. CustomerID = "LAWN", 14. CompanyName = "Lawn Wranglers", 15. ContactName = "Mr. Abe Henry", 16. ContactTitle = "Owner", 17. Address = "1017 Maple Leaf Way", 18. City = "Ft. Worth", 19. Region = "TX", 20. PostalCode = "76104", 21. Country = "USA", 22. Phone = "(800) MOW-LAWN", 23. Fax = "(800) MOW-LAWO" 24. ; // 3. Dodanie obiektu encyjnego do tabeli Customers. 25. db.customers.insertonsubmit(cust); // 4. Wywołanie metody SubmitChanges (). 26. db.submitchanges(); // 5. Zapytanie recordu. 27. Customer customer = db.customers.where(c => c.customerid == "LAWN").First(); 28. Console.WriteLine("0-1", customer.companyname, customer.contactname); // Ta część kodu odzyskiwa bazę danych od prowadzonych zmian 29. Console.WriteLine("Deleting the added customer LAWN."); 30. db.customers.deleteonsubmit(cust); 31. db.submitchanges();console.writeline("0 : End", new StackTrace(0, true).getframe(0).getmethod().name); 32. Console.ReadLine(); W linii 9 jest stworzony kontekst bazy danych. Baza danych zawiera kolekcji tablic encji. W liniach 11 stworzona jest instancja obiektu customer. W linii 25 instancja obiektu customer została dodana do tablicy Customers za dopomogą metody InsertOnSubmit(). W linii 26 zmiany zostali wprowadzone do bazy danych. W liniach 27-28 wpisany rekord został ponownie wywołany z bazy danych i oddrukowany na konsoli. W liniach 29-30 wpisany rekord jest usunięty z bazy. Przypuścimy trzeba dodać instancję obiektu order. Można to z realizować w sposób pokazany w listingu 5 bezpośrednio do obiektu order. Można to zrobić w sposób inny, zwłaszcza można dodać ten obiekt do kolekcji orders wyznaczonej instancji obiektu Customer. W listingu 11 jest pokazany przykład wstawiania rekordu przez kolekcje obiektów skojarzonych z wyznaczonej encją. Listing 11. using System; 1. using System.Collections.Generic; 2. using System.Data.Linq; 3. using System.Diagnostics; // For the StackTrace. 4. using System.Linq; 5. using System.Text; 6. using nwind; 20
7. Console.WriteLine("0 : Begin", new StackTrace(0, true).getframe(0).getmethod().name); 8. Northwind db = new Northwind(@"server=localhost;uid=sa;pwd=asdf;database=Northwind"); 9. Customer cust = (from c in db.customers where c.customerid == "LONEP" select c).single<customer>(); // Used to query record back out. 10. DateTime now = DateTime.Now; 11. Order order = new Order CustomerID = cust.customerid, EmployeeID = 4, OrderDate = now, RequiredDate = DateTime.Now.AddDays(7), ShipVia = 3, Freight = new Decimal(24.66), ShipName = cust.companyname, ShipAddress = cust.address, ShipCity = cust.city, ShipRegion = cust.region, ShipPostalCode = cust.postalcode, ShipCountry = cust.country ; 12. cust.orders.add(order); 13. db.submitchanges(); 14. IEnumerable<Order> orders = db.orders.where(o => o.customerid == "LONEP" && o.orderdate.value == now); 15. foreach (Order o in orders) 16. 17. Console.WriteLine("0 1", o.orderdate, o.shipname); 18. // This part of the code merely resets the database so the example can be // run more than once. 19. db.orders.deleteonsubmit(order); 20. db.submitchanges(); 21. Console.WriteLine("0 : End", new StackTrace(0, true).getframe(0).getmethod().name); 22. Console.ReadLine(); W linii 9 jest odszukana w bazie danych instancja obiektu customer z identyfikatorem "LONEP".W linii 11 jest stworzona instancja obiektu order. W linii 12 do kolekcji orders obiektu customer została dodana stworzona instancja za dopomogą metody ADD. Wracamy uwagę na to, że dodania do kolekcji Table<T> było za dopomogą metody InsertOnSubmit() (patrz listing 5. Linia 25) oraz do kolekcji EntitySet<T> przez metodę Add(). Pobieranie baz Northwind oraz Pub oraz dołączenie tych baz do MS SQL Server 2008. Standardowa instalacja MS SQL Server 2008 nie zawiera przykładowych baz Northwind oraz Pub którzy byli w starszych wersjach MS SQL Server 2000. Dla doinstalowania tych baz do MS SQL Server 2005 trzeba realizować następne kroki: 21
1.Uruchomić stronę WWW http://www.microsoft.com/poland/technet/bazawiedzy/centrumrozwiazan/cr122_01.mspx 2.Odszukać rozdział na stronie przeznaczony do pobierania tych baz: 3.Urochomić pokazany wyżej link do strony z zawartością pliku SQL2000SampleDb.msi oraz urochomić proces kopiowania tego pliku. 4. Plik SQL2000SampleDb.msi musi być razarchiwowany na dysku c:\. Po rearchywacji na dysku c: będzie stworzony katalog C:\SQL Server 2000 Sample Databases. W tym katalodzie są pliki instnwnd.sql oraz instpubs.sql: 5. Dla dołączenia pliku bazy danych Northwind (lub Pubs) do MS SQL Server 2005 trzeba kliknąć dwa razy instnwnd.sql (instpubs.sql) dla uruchomienia Sql Management Studio 2005. W środowiśku Management Studio zalądować plik instnwnd.sql (instpubs.sql) oraz uruchomić go przez EXECUTE. Plik bazy danych zostanie dołączony do serwera. LINQ to DataSet Korzystając z ADO.NET można prezentować i modyfikować dane z bazy danych. 22
Zadania ćwiczeniowe dla studenta 1.LINQ to Object Stwórz program w którym jest zdefiniowana tablica ciągów znaków. W zawartość tej tablicy wpisz nazwiska studentów swojej grupy. Za dopomogą polecenia LINQ to Object wydrukuj wszystkie nazwiska studentów poczynające z wyznaczonej litery. Wskazówka: typowa konstrukcja kodu do wywołania elementu z tablicy ciągów znaków może mieć następny wygląd: IEnumerable<string> items = grupa.where(p => p.startswith("a")); foreach (string item in items) Console.WriteLine(item); 2.LINQ to XML Stwórz plik XML z nazwami przedmiotów oraz wykładowców. Za dopomogą polecenia LINQ to XML wydrukuj przedmioty danego wykładowcy oraz wykładowców skojarzonych z tym przedmiotem. 3.LINQ to SQL z wykorzystaniem SQLMETAL Dołącz bazę danych Northwind do MS SQL Server 2008 Stworz nowy projekt w Visual Studio 2012 Stworz kontekst bazy za dopomogą utility Sqlmetal Dołącz plik kontekstu do projektu Stwórz kod programu do wywołania wszystkich zamowień wyznaczonego klienta Stwórz kod programu do wstawiania nowego zamowienia 4. LINQ to SQL z wykorzystaniem O/R Designera Stworz nowy projekt w Visual Studio 2012 Za dopomogą O/R Designera dołącz niezbędne obiekty Stwórz kod programu do jednoczesnego wstawiania nowego klienta razem z trzema zamowieniami Stwórz kod programu do wywołania wszystkich towarów zamowianych klientem w wyznaczonym zamowieniu 5. Projektowanie zapytań LINQ Technologia LINQ została stworzona, aby ułatwić czytanie kodu, w którym znajdują się kawałki SQL. Dlatego wyrażenia LINQ mają podobna strukturę, co wyrażenie select w SQL. Można między innymi: Wybierać dane na podstawie warunku. Sumować wyniki Wyszukiwać Min i Max Stosować join Grupować dane Sortować dane Poszczególne lekcje odpowiadają określonej grupie zadań. 23