ADO.NET wprowadziło nowe kontenery danych: DataSety i to w dwóch wersjach: zwykłe i typowane. Szczególnie te drugie przypadły mi do gustu i właśnie z nich najczęściej korzystam.
W pierwszych aplikacjach ładowałem dane do tych datasetów bezpośrednio w kodzie kontrolek prezentujących dane dla użytkownika. Takie rozwiązanie ma trochę zalet, np. szybsza implementacja prostych kontrolek, łatwiejsze grupowanie kontrolek w większe komponenty (bo każda kontrolka sama ładuje dane). Jednak mieszanie warstwy prezentacji z warstwą logiki biznesowej zaczęło mi coraz bardziej doskwierać, stąd zacząłem się zastanawiać nad zmianą koncepcji.
Zainteresowałem się wzorcem Model View Controller, jednak do tej pory nie jestem w stanie się do niego przekonać (ale niewykluczone, że kiedyś tak się stanie). Jeśli dobrze rozumiem MVC, to kontroler jest obiektem, który pobiera dane z modelu i prezentuje w widoku. Ale przecież .NET Framework obsługuje wiązanie kontrolek do źródeł danych (obiektów, w tym datasetów).
Na początek postanowiłem podzielić kod tylko na odrębne klasy widoki i kontrolera oraz z datasetów przenoszących dane między nimi:
W tej koncepcji kontroler ładuje dataset i przekazuje go do widoku, zajmuje się też zapisem danych oraz wykonywaniem innych poleceń, natomiast widok odpowiada za prezentację danych.
Gdy zaczynałem korzystać z tego podejścia miałem do dyspozycji VS2003. Po położeniu na formę odpowiedniego datasetu (np. o nazwie myDataSet) można było do niego przybindować poszczególne kontrolki wizualne (datagridy, textboxy, comboboxy itp). Dopóki ładowanie danych odbywało się w samej kontrolce, to nie było problemu, jednak po separacji widoku i kontrolera pojawił się problem z przekazaniem danych do widoku.
Jednak proste przypisanie this.myDataSet = value nie aktualizowało bindingu, a jedynie zmieniało zawartość prywatnej zmiennej myDataSet - teraz wskazywała ona inny obiekt, jednak kontrolki w dalszym ciągu były zbindowane do obiektu tej samej klasy, ale utworzonego przez kod wygenerowany przez designera. Aby rozwiązać ten problem najpierw zacząłem kopiować z designera kod bindujący i ponawiałem bindowanie po każdorazowej zmianie datasetu źródłowego.
Potem znalazłem inne rozwiązanie w postaci wykorzystania metody Merge. A więc kontroler przekazywał do widoku swoją instancję klasy MyDataSet, a widok kasował zawartość swojego datasetu źródłowego i dołączał wszystkie rekordy z datasetu przekazanego przez kontroler. Oczywiście przy zapisie danych sekwencja zdarzeń musiała być odwrotna: kontroler pobierał dataset źródłowy wiew i dołączał go wyczyszczonego swojego datasetu, a potem zapisywał dane do bazy danych.
Ta gimnastyka z danymi jest na pewno nieunikniona przy aplikacji gdzie warstwy są wyodrębnione i np. znajdują się na osobnych maszynach, natomiast w przypadku mniejszych aplikacji miałem wrażenie, że tylko zaciemniają zrozumienie kodu, poza tym dane się rozmnożyły - kontroler miał jedną wersję danych, a widoki inną, dodatkowo z tych samych danych może korzystać kilka widoków np. komponent obsługujący dane klientów oraz komponent do obsługi faktur, a równocześnie istniała potrzeba by wszystkie widoki prezentowały tą samą wersję danych, a więc po wprowadzeniu nowego klienta powinien on być od razu dostępny w komponencie faktur. Zatem, pojawiają się problemy z synchronizacją danych.
Na ratunek przyszedł .NET 2.0 wraz z nowym obiektem BindingSource. Teraz kontrolki można bindować nie bezpośrednio do datasetu, lecz do myBindingSource, a dopiero do tegoż obiekt można przypiąć dataset. Kontroler przy otwarciu widoku ustawia jego właściwość MyDataSet, a w kodzie tej właściwości ustawiana jest nowa wartość DataSource obiektu myBindingSource. I to tyle - wszystko działa jak należy - kodu jest znacznie mniej, nie ma konieczności przebindowywanie kontrolek, a równocześnie wszystkie widoki obsługiwane przez tego samego kontrolera mają takie same dane.
A czy teraz metoda Merge pozostaje u mnie bez pracy? Nie, przydaje się przy asynchronicznym ładowaniu danych do datasetu. Kontroler ładuje dane w osobnym wątku do nowo utworzonego datasetu, a po ich załadowaniu za pomocą Invoke przechodzi na wątek GUI i tam metodą Merge dodaje załadowane dane.
Na koniec taka refleksja: już niedługo wchodzi Orcas, a w nim zupełnie nowe ADO.NET Entity Framework, a więc konieczność porzucenia starych koncepcji (w tym tej powyższej) i żmudne wypracowanie nowych rozwiązań :-).

1 komentarze:
"Jeśli dobrze rozumiem MVC, to kontroler jest obiektem, który pobiera dane z modelu i prezentuje w widoku" - no nie do końca, widok umie pobierać dane z modelu. Kontroler służy przede wszystkim do obsługi reakcji użytkownika (dlatego Fowler używa pojęcia Input Controller).
BTW: w .net'e częściej się wykorzystuje chyba MVP niż MVC.
Prześlij komentarz