Linq to SQL / Entity Framework / DataSet - Part 1

Linq to SQL / Entity Framework / DataSet
A może jeszcze NHibarnate?
Rozpoczęłam swoją przygodę z MVC od stworzenia jakiegoś HelloWorld według tutoriali na stronie ASP.NET. I pierwsze co wygenerowałam - to błędy przy Update, Insert.
Postanowiłam więc zgłębić zagadnienie literki M
Każda książka czy tutorial związana z MVC gdy natrafia w opisach na literkę M tworzy bez zastanowienia Entity Data Model.
ASP.NET MVC framework oferuje dostęp do takiego modelu jaki sobie tylko zamarzymy i zaimplementujemy : ADO.NET, DataSet, obiekty DataReader, obiekty domenowe, ORM czyli object-relational mappers, LINQ to SQL i tak dalej...
Jednak najpowszechniejszą kursową techniką jest model domenowy.
Postanowiłam więc przyjrzeć się co to takiego i dlaczego


Największe doświadczenie jak do tej pory posiadam w pracy z typowanymi DataSetami. Tworzymy sobie DataSeta z wszelkimi potrzebnymi w danym zastosowaniu tabelkami, łączymy je relacjami, przestawiamy kaskadowe usuwanie, tworzymy dla każdej tabelki minimum jedną procedurę składowaną dla polecenia Update, zazwyczaj też Insert i Delete no i jeden wielki Select. Sporo tego już, ale trzeba jeszcze zadbać o mapowanie danych i podpięcie procedur do aktualizacji danych do każdej tabelki...
Do wszystkiego można się przyzwyczaić.

LINQ to SQL pojawił się już jakiś czas temu bo jak się nie mylę z VS2008, conieco się gdzieś o nim słyszało .. Zaczęłam więc tutaj.
1. LINQ - technologia zapytań zintegrowanych z językiem. Sam w sobie LINQ to składnia, którą można wykorzystać do odpytywania różnych źródeł danych (baza, obiekty, XML, itd.).
2. LINQ to SQL - technologia mapująca bazę danych SQL Servera do postaci obiektowej i pozwalająca na odpytywanie bazy danych za pomocą składni LINQ.
Pięknie, no to pierwszy tutorial
Add New Item
Otwiera się 'powierzchnia podobna do tej do budowania DataSeta, wyciągam więc sobie tabelki z Server Explorera.. tworzy się mapowanie z wszystkimi powiązaniami. Mogę sobie też dołożyć kilka procedur skłądowanych.
Pierwsza miło niemiła rzecz jaką robi Linq to zmiana liczby w nazwie tabeli - było Application jest Applications, gdyby było na odwrót na odwrót by się zmieniło. Rozumiem założenie, choć może być to denerwujące.
Robimy pierwszy select. Oczywiście aplikacja jest webowa i używam poczciwego DataGrida.
 protected void Button1_Click(object sender, EventArgs e)
    {
        MojModelDataContext fgsp = new MojModelDataContext();
        var application = (from p in fgsp.Applications
                          select p);
        dataGrid.DataSource = application;
        dataGrid.DataBind();
    }
Eureka - działa. Coś trudniejszego.
 protected void Button2_Click(object sender, EventArgs e)
    {
        // Company jest powiązane z application więc możemy od razu zadać
        // pytanie linqowe z danymi z company
        MojModelDataContext fgsp = new MojModelDataContext ();
        var application = from p in fgsp.Applications
                          where p.Company.ShortName == "jakisShort".ToUpper()
                          select p;
        /*behind the scene
         zapytanie jest przerabiane na joina
         
         {SELECT 
           [t0].[ID], ... i tak dalej
         FROM [Application] AS [t0]  
         INNER JOIN [Company] AS [t1] ON [t1].[ID] = [t0].[CompanyID]  
         WHERE [t1].[ShortName] = @p0  }

         */
        dataGrid.DataSource = application;
        dataGrid.DataBind();       
    }
Proste i działające, zróbmy sobie kawałek pagowania
    protected void Button5_Click(object sender, EventArgs e)
    {
        MojModelDataContext fgsp = new MojModelDataContext ();
        int startRow = 0;
        var applicationdic = from p in fgsp.Applications                            
                             select p;

        dataGrid.DataSource = applicationdic.Skip(startRow).Take(4);
        dataGrid.DataBind();
    }
Jak widać pagowanie może być niezwykle proste - pomijamy ileśtam, bierzemy ileśtam. Wiadomo ze zapytanie linq wykonuje się w chwili odwołania do niego a nie w miejscu definicji co oznacza ze skip take - generuje nam sqla który pobiera tylko i wyłącznie zadana paczkę danych. To jaki generuję się SQL to już inna sprawa i polecam podpatrzenie dzięki SqlServerQueryVisualizer (SqlServerQueryVisualizer.dll należy wrzucić do \Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers\)

No to teraz Update
    protected void Button6_Click(object sender, EventArgs e)
    {
        MojModelDataContext fgsp = new MojModelDataContext ();

        var applicationdic = from p in fgsp.Applications
                             select p;
        foreach (Application item in applicationdic)
        {
            item.ChangeDate = DateTime.Now; //null;// 
            //od razu mamy walidacje z obiektu - nullowalność, typy
            //a w dodatku w końcu!!
            //typy nullowalne np DateTime?
        }
        try
        {
            fgsp.SubmitChanges(); // czyli tutaj robi sie wszystko aby updatnac obiekt
        }
        catch (Exception ex)
        {
            string s = ex.Message;
        }
    }
Operacje na obiekcie wykonywane są lokalnie co jest całkiem logiczne, aby zostały wysłane do db musi zostać wywołana SubmitChanges() - funkcja (a raczej krasnoludki w niej ukryte) decyduje co zostało zmienione i wysyła odpowiednie updaty inserty do db. Jeśli nie było zmiany - nie zostanie wykonany update
Przekazujemy wszystkie kolumny - wszystkie dane w updacie
Optymistic concurrency - Jeśli zaszły jakieś zmiany w obiekcie dostajemy Exception System.Data.Linq.ChangeConflictException
SubmitChanges - wykonuje się w transakcji jeśli coś nie poszło - całość się cofa.

Pozostaje Insert
    protected void Button7_Click(object sender, EventArgs e)
    {
        MojModelDataContext fgsp = new MojModelDataContext ();

        var apphis = fgsp.Applications.Single(p => p.ID == 952);
        //tym razem historia 
        ApplicationListChangesHistory his = new ApplicationListChangesHistory();
        his.Application = apphis;
        his.ChangeUserDate = DateTime.Now;
        
        fgsp.ApplicationListChangesHistories.InsertOnSubmit(his);        //dodajemy nowo stworzona instancje  do obiektu
        try
        {
            fgsp.SubmitChanges(); //i wywolujemy "update
        }
        catch (Exception ex) 
        {
            string s = ex.Message;
        }
    }


Możliwe jest też rozszerzanie modelu. Mamy do wyboru funkcje typu OnPropertieChanged() OnPropertieChanged() OnCreated() OnValidation() można coś potworzyć sensownego.

Dodajmy sobie jeszcze swoją procedurkę do obsługi np Inserów
Na "tabelce" w menu podręcznym wybieramy Configure Behavior lub w oknie Properties wybieramy Insert...

Pojawia się okno konfiguracji w którym domyślnie ustawione jest "Use runtime". Po przestawieniu na Customize mamy możliwość wyboru funkcji (wcześniej dodanej do naszego dbml modelu). Jeśli pola w metodzie i pola w obiekcie zgadzają się co do nazwy - zostaną automatycznie podmapowane, jeśli nazwy się nie zgadzają musimy to sobie wyklikać.

Możliwe jest wybranie np tylko funkcji do Insert wtedy Update i Delete nadal będą generowane automatycznie at runtime.

Stety/Niestety nie ma takiej samej opcji dla select.

Porównując tą metodę do pobierania wszystkiego do DataSetu... cóż tu dużo mówić nie trzeba się tyle nastukać rzeczy autogenerujących się. Oczywiście nie jesteśmy pozostawieni na łasce tego co zostanie wygenerowane, możemy sobie podpiąć własne procedury składowane do Insertów Updatów Deletów na poszczególnych tabelkach - ale możemy znaczy nie musimy. Do tego pagowanie
Ale to tylko aplikacja 'na pokaz' nie ma tu nic skomplikowanego, na pewno problemy pojawiły by się potem, jednak czy nie lepiej być optymistą ;)

Komentarze

  1. Spozniony komentarz, ale...

    Dorabianie liczby mnogiej mozna oczywiscie wylaczyc -- jest to opcja tak irytujaca, ze pierwsza czynnosc jaka zrobilem to google pod tym katem. Zmiana dziala i z poziomu VS i mozna z reki podac to SQLMetalowi.

    OdpowiedzUsuń

Publikowanie komentarza

Popularne posty