Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Jakub Binkowski - dot or not

Blog programisty C#

opublikowano 10 maja 2009 22:32
Czy warto organizować Speaker Idole?

W ostatni czwartek odbył się konkurs Speaker Idol w Łodzi, w wyniku którego wyłoniliśmy prezentera do CodeCamp 2009 Warszawa. Było to pierwsze tego typu wydarzenie organizowane przez Łódzką Grupę Specjalistów IT & .NET oraz pierwsze, w którym brałem udział (zarówno jako uczestnik jak i organizator). Pomyślałem, że jest to dobra okazja do podzielenia się swoimi przemyśleniami, zwłaszcza, że wiele z lokalnych grup (zwanych też nie po polsku “społecznościami offline”) jeszcze tego typu imprezy u siebie nie organizowało.

Tytułem wstępu jedno zdanie o samej idei Speaker Idol. Jest to konkurs, polegający na tym, iż uczestnicy wygłaszają krótkie prezentacje na wybrany przez siebie temat, a publiczność wybiera w głosowaniu zwycięzcę. Koniec wstępu.

Plany, nadzieje, obawy

Podejmując decyzję o ogłoszeniu konkursu liczyliśmy na osiągnięcie trzech celów:

  1. Wyłonienie nowych potencjalnych prelegentów spośród członków grupy
    Wyszliśmy z założenia, że przygotowanie 5-10 minutowej prezentacji, nie wymaga aż tak wiele czasu i wysiłku jak w wypadku godzinnej prezentacji, a perspektywa nagród na pewno zadziała zachęcająco. Poza tym osoba, która już raz stanęła przed publicznością, może chcieć spróbować własnych sił po raz kolejny – tym razem przygotowując pełną sesję.
  2. Zwiększenie liczby aktywnych członków grupy.
    Wszyscy liderzy chcieliby, aby sale podczas spotkań ich grupy pękały w szwach i nie było to spowodowane przeniesieniem się do mniejszej sali ;). Speaker Idol oraz wiążące się z nim kilka krótkich prezentacji na różne tematy, to na pewno nowa formuła spotkania. Liczyliśmy, że będzie zachęcająca dla obecnych członków.
  3. Wyłonienie zwycięzcy, który przygotuje dobrą sesję na CodeCamp.

Najbardziej obawialiśmy się tego, że nikt się nie zgłosi. W końcu znacznie bardziej prestiżowe edycje Speaker Idoli (przed MTS czy C2C) miały większe lub mniejsze problemy z rekrutacją uczestników. W tym celu podjęliśmy kilka kroków zaradczych, które w większości się sprawdziły:

  • Konkurs ogłosiliśmy z ponad miesięcznym wyprzedzeniem. 
    Oczywiście, prawda jest taka, że każdy i tak  swoją prezentację opracowywał maksymalnie tydzień wcześniej, ale świadomość posiadania większej ilości czasu daje większe poczucie swobody.
  • Konkurs odbył się zaraz po długim weekendzie.
    Dzięki temu było więcej czasu na przygotowanie prezentacji w ostatnim krytycznym tygodniu.
  • Postaraliśmy się, aby nagrody były ciekawe (własna sesja na CodeCamp, MSDN Premium z Visual Studio Team Suite, 7xWindows Vista Ultimate i inne).

Poza tym baliśmy się też tego, czy prezentacje będą na dobrym poziomie, zwłaszcza, że wysyłamy naszego reprezentanta-zwycięzcę na CodeCamp w Warszawie.

Ostatnią naszą obawą była frekwencja na spotkaniu, tzn. czy publiczność (jurorzy) dopisze?

Jak wyszło?

Zgłoszeń w sumie było 5, czyli wynik nie taki zły. Oczywiście, prawie wszystkie pojawiły się praktycznie w ostatniej chwili, ale jest to chyba normalne (zresztą sam swoją zgłosiłem kilka dni przed ostatecznym terminem). Prezentacje dotyczyły następujących tematów:

  1. “Gdy DataGridView nie wystarczy - prezentacja pakietu DXperience” (Rafał Sańda).
  2. “Przyspieszamy aplikacje Web 2.0” (moja).
  3. “Checked exceptions w C#” (Bartek Legiędź).
  4. “XP Mode w Windows 7” (Darek Porowski).
  5. “Jak się ma dzisiaj moja firma ? - czyli jak wykorzystywać wskaźniki w SSRS 2008” (Mariusz Koprowski).

Muszę przyznać, że obawa o poziom była absolutnie nieuzasadniona! Każda prezentacja (oczywiście nie piszę tu o własnej, bo nie mi ją oceniać) była bardzo dobrze przygotowana, interesująca i z przyjemnością się jej słuchało. Całkowicie zasłużenie zwyciężył Bartek, który wybrał świetny temat, bardzo ciekawie go opracował i przy okazji ma naturalne zdolności do prowadzenia sesji.

Co do ilości zgłoszeń - 5 to całkiem niezła liczba, jednak wszystkie zgłoszenia pochodziły od tak zwanego “core” grupy, czyli osób najbardziej zaangażowanych w jej życie. Trzeba tu jednak zaznaczyć, iż to nikt z nas nie wystartował w konkursie, żeby nie było pusto, ale dlatego, że miał ciekawy temat, którym chciał się podzielić z innymi. Spośród uczestników był tylko jeden - Rafał, który nie przygotował wcześniej żadnej prezentacji na spotkaniach grupy.

Ostatnią obawą była frekwencja. Niestety, na spotkanie przyszło trochę mniej osób niż zwykle, ale tutaj bardziej obwiniłbym piękną wiosenną pogodę niż samą formułę Speaker Idola.

Najważniejsze pytanie – czy warto?

Z czystym sumieniem mogę stwierdzić, że nasz Speaker Idol był sukcesem, choć trochę innym niż oczekiwałem. Z jednej strony tylko częściowo udało nam się zrealizować postawione cele, ale z drugiej wyszło jedno z ciekawszych spotkań, przynajmniej wg mnie.

Co się udało?

  • Wyłoniliśmy naprawdę dobrego reprezentanta na CodeCamp.
  • Zorganizowaliśmy bardzo ciekawe spotkanie, które ze względu na swoją formułę różniło się od pozostałych.
  • W moim odczuciu trochę bardziej zintegrowaliśmy się jako grupa (w końcu społeczność to nie sala i prezentacje, tylko ludzie i relacje między nimi).
  • Nowa osoba spróbowała swoich sił w roli prelegenta i przygotowała naprawdę niezłą sesję.

Co się nie do końca udało?

  • W konkursie nie wzięły udziału “nowe twarze”, na co, przyznam szczerze, dosyć mocno liczyliśmy.
  • Frekwencja mogła być trochę lepsza. Albo pogoda albo nowa formuła nie przekonała do siebie niektórych. Ich strata :P.

Czy warto było organizować Speaker Idol? Zdecydowanie tak!

przez jakubin | 7 komentarzy

Ballmer, Ballmer, Ballmer

Bilet PKP Łódź-Warszawa-Łódź - 60 zł.

Czas poświęcony na przejazdy - 3,5 godz.

Możliwość zadania pytania prezesowi Microsoft - bezcenna?

No właśnie - pytanie brzmi: czy warto było przybyć na 50 minutowe spotkanie ze Stevem Ballmerem? Wszystko zależy od tego, czego oczekiwało się po tym spotkaniu. Za tym, żeby poświęcić pół dnia (w tym bezcenny poranek) przemawiały:

  • możliwość zobaczenia na żywo człowieka, który "stoi za tym wszystkim",
  • możliwość zadania mu pytania i
  • możliwość w przyszłości rzucenia mimochodem podczas rozmowy "podczas jednego z moich ostatnich spotkań z Ballmerem mówił, że..." ;).

Natomiast siedzący na drugim ramieniu diabełek (pingwin?) podpowiadał:

  • wyśpij się,
  • i tak nie dowiesz się niczego wybitnie nowego,
  • będzie transmisja on-line,
  • wyśpij się,
  • to jest spotkanie otwarte, więc Steve Ballmer nie będzie mógł zdradzić żadnych tajemnic.

Raz się żyje - zarejestrowałem się i zgłosiłem własne 4 pytania. Ku mojemu zaskoczeniu, do zadania wytypowane zostały aż dwa i to dokładnie te, na których najbardziej mi zależało.

Przybyłem na miejsce. Po prezentacji kilku klipów promocyjnych Microsoftu, na salę wszedł Steve w... biało-czerwonym szaliku z napisem "akslop". Na szczęście, w odróżnieniu od pewnego wysoko postawionego polskiego polityka, zauważył swój błąd, odwrócił szalik i okazało się że "akslop" to "Polska". Wstęp całkiem miły. Potem powiedział parę słów od siebie - coś o wizji, przyszłości, kryzysie. Następnie przeszliśmy do pytań. W sumie było ich ponad 10, z czego ja pytałem się o:

  1. Co sądzi Pan o patentach na oprogramowanie?
    Trochę liczyłem tutaj na krytykę postawy "patentuj wszystko". Dostałem odpowiedź: "Patenty są bardzo ważne dla branży, jednakże prawodawstwo wymaga dopracowania, gdyż czasami prowadzi do nadużyć.". Nic odkrywczego, choć trudno się z tą odpowiedzią nie zgodzić.
  2. Jaka będzie przyszłości Internet Explorera? Czy będziecie go rozwijać, czy też kupicie Operę lub stworzycie coś własnego?
    Na to pytanie odpowiedź była konkretna: "tak - będziemy rozwijać IE, nie - nie kupimy Opery, bo nie zgodziłyby się na to instytucje antymonopolowe". Dodatkowo Steve zasugerował, że Microsoft stworzy coś nowego (czyżby Gazelle?) albo źle go zrozumiałem.

Co jeszcze zapadło mi w pamięci? Odpowiedź na pytanie, czy kryzys wpłynie na popularność darmowych rozwiązań - "nie - tylko zwiększy skalę piractwa". A także prośba o określenie całego spektrum działań Microsoftu jednym słowem. Ktoś na sali podpowiedział "awesome", co podchwycił i błyskotliwie rozwinął Steve - "awesome, baby!".

Generalnie prezes Microsoftu zrobił na mnie dokładnie takie wrażenie, jakiego się spodziewałem. Jest to człowiek o niezwykłej charyzmie, dużej spontaniczności, przechodzącej czasami w lekką odmianę szaleństwa (developers, developers, developers), potrafiący być momentami rozbrajająco szczery oraz świetnie czującym publiczność.

Czy było warto? Uważam, że tak.

przez jakubin | 5 komentarzy

Filed under: ,

Sesja na CodeCamp, MSDN Premium i Windows Vista w 10 minut, czyli Speaker Idol Łódź!

silodz

W imieniu Łódzkiej Grupy Profesjonalistów IT & .NET mam zaszczyt zaprosić wszystkich do wzięcia udziału w konkursie Speaker Idol 2009!

Wystarczy przygotować krótką prezentację:

  • na dowolny temat,
  • trwającą max. 10 minut
  • i wygłosić ją na spotkaniu Grupy 7 maja.

Co można wygrać?

  • Własna sesja na CodeCamp 2009 Warszawa!
  • Roczna subskrypcja MSDN Premium z Visual Studio 2008 Team Suite*
    MSDN Premium zawiera systemy operacyjne Microsoft, serwery i narzędzia z przeznaczeniem do tworzenia oprogramowania i testowania, Microsoft Office do dowolnego zastosowania biznesowego, 4 zdarzenia pomocy technicznej i inne.
  • 7 x Windows Vista Ultimate*
  • Windows Compute Cluster Server 2003*
  • JetBraints ReSharper*
  • Perpetuum Software*
  • CodeSmith Tools*
  • Plecaki Microsoft

* licencje NFR (not for resale - bez prawa odsprzedaży), nie mające wartości rynkowej.

Więcej informacji znajduje się na stronie Łódzkiej Grupy Profesjonalistów IT & .NET.

przez jakubin | 0 komentarzy

opublikowano 22 lutego 2009 23:07
String.Format() w .NET i JavaScript (ASP.NET AJAX)

Swojego czasu z wielką radością odkryłem, iż biblioteka ASP.NET AJAX dostarcza genialne funkcje w JavaScript, działające dokładnie tak samo jak String.Format w .NET:

  • String.format(format, args) - formatuje tekst,
  • String.localeFormat - formatuje tekst używając bieżącej kultury UI (w ScriptManager należy dać EnableScriptGlobalization="true" oraz wybrać odpowiednią kulturę na poziomie strony lub aplikacji).

Innymi słowy pisząc w BLOCKED SCRIPT

String.formatLocale("Aktualny miesiąc to {0:MMMM yyyy}", new Date())

uzyskujemy oczekiwany tekst - np. "Aktualny miesiąc to luty 2009". Genialne, prawda?

Niestety, wersja JavaScript jest lekko upośledzonym odpowiednikiem swojego .NETowego brata :(. Po pierwsze, nie obsługuje wszystkich ciągów formatujących - to akurat można przeżyć. Po drugie, w języku polskim w przypadku dat popełnia błędy gramatyczne, co już jest większym problemem... Jakie błędy? Oto proste zestawienie:

Format .NET JavaScript
d 2009-02-22 2009-02-22
D 22 lutego 2009 22 luty 2009
f 22 lutego 2009 22:07 -
F 22 lutego 2009 22:07:19 22 luty 2009 22:07:20
g 2009-02-22 22:07 -
G 2009-02-22 22:07:19 -
M 22 lutego 22 luty
O 2009-02-22T22:07:19.9917540+01:00 -
R Sun, 22 Feb 2009 22:07:19 GMT -
s 2009-02-22T22:07:19 2009-02-22T22:07:20
t 22:07 22:07
T 22:07:19 22:07:20
u 2009-02-22 22:07:19Z -
U 22 lutego 2009 21:07:19 -
Y luty 2009 luty 2009
MMMM luty luty
d MMMM yyyy 22 lutego 2009 22 luty 2009
MMMM yyyy luty 2009 luty 2009

Jak widać różnica dotyczy przypadku, w którym wyświetlany jest dzień i nazwa miesiąca. Poprawna jest wersja generowana przez metodę w .NET - "22 lutego 2009", czyli miesiąc powinien być w dopełniaczu. JavaScript używa niepoprawnego przypadku - mianownika ("22 luty 2009"). Najdziwniejsze w tym wszystkim jest to, że nic nie stało na przeszkodzie, aby formatowanie było poprawne. Dane dotyczące kultury zawierają nazwy miesięcy w dopełniaczu:

var __cultureInfo = '{"name":"pl-PL",
"numberFormat":{"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":",","IsReadOnly":true,"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],"PercentGroupSizes":[3],"CurrencyGroupSeparator":" ","CurrencySymbol":"zł","NaNSymbol":"nie jest liczbą","CurrencyNegativePattern":8,"NumberNegativePattern":1,"PercentPositivePattern":1,"PercentNegativePattern":1,"NegativeInfinitySymbol":"-nieskończoność","NegativeSign":"-","NumberDecimalDigits":2,"NumberDecimalSeparator":",","NumberGroupSeparator":" ","CurrencyPositivePattern":3,"PositiveInfinitySymbol":"+nieskończoność","PositiveSign":"+","PercentDecimalDigits":2,"PercentDecimalSeparator":",","PercentGroupSeparator":" ","PercentSymbol":"%","PerMilleSymbol":"‰","NativeDigits":["0","1","2","3","4","5","6","7","8","9"],"DigitSubstitution":1},
"dateTimeFormat":{
    "AMDesignator":"","Calendar":{"MinSupportedDateTime":"\/Date(-62135596800000)\/","MaxSupportedDateTime":"\/Date(253402297199999)\/","AlgorithmType":1,"CalendarType":1,"Eras":[1],"TwoDigitYearMax":2029,"IsReadOnly":true},"DateSeparator":"-","FirstDayOfWeek":1,"CalendarWeekRule":2,"FullDateTimePattern":"d MMMM yyyy HH:mm:ss","LongDatePattern":"d MMMM yyyy","LongTimePattern":"HH:mm:ss","MonthDayPattern":"d MMMM","PMDesignator":"","RFC1123Pattern":"ddd, dd MMM yyyy HH\u0027:\u0027mm\u0027:\u0027ss \u0027GMT\u0027","ShortDatePattern":"yyyy-MM-dd","ShortTimePattern":"HH:mm","SortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss","TimeSeparator":":","UniversalSortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd HH\u0027:\u0027mm\u0027:\u0027ss\u0027Z\u0027","YearMonthPattern":"MMMM yyyy",
    "AbbreviatedDayNames":["N","Pn","Wt","Śr","Cz","Pt","So"],
    "ShortestDayNames":["N","Pn","Wt","Śr","Cz","Pt","So"],
    "DayNames":["niedziela","poniedziałek","wtorek","środa","czwartek","piątek","sobota"],
    "AbbreviatedMonthNames":["sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru",""],
    "MonthNames":["styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień",""],
    "IsReadOnly":true,"NativeCalendarName":"Kalendarz gregoriański",
    "AbbreviatedMonthGenitiveNames":["sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru",""],
    "MonthGenitiveNames":["stycznia","lutego","marca","kwietnia","maja","czerwca","lipca","sierpnia","września","października","listopada","grudnia",""]}}';

Niestety, wartości z pola MonthGenitiveNames nie są nigdzie potem wykorzystywane w kodzie JavaScipt ASP.NET AJAX, co świadczy o pewnym niechlujstwie twórców biblioteki.

przez jakubin | 3 komentarzy

Filed under: , , ,

opublikowano 21 lutego 2009 21:23
Eleganckie samobójstwo aplikacji ASP.NET

Czasami może się zdarzyć, że nasza aplikacja ASP.NET odczuje potrzebę popełnienia samobójstwa i narodzenia się na nowo. Kiedy? Na przykład, gdy zmieni się jakiś plik konfigurujący aplikację (nie dotyczy to web.config, którego zmiana powoduje automatyczny restart) lub gdy chcemy w sekcji administracyjnej witryny dać możliwość administratorowi wykonania tego na życzenie. Wówczas pojawia się pytanie, jak to zrobić elegancko i zgodnie z lege artis?

Rozwiązanie łopatologiczne polega na modyfikacji pliku web.config (choćby metodą File.SetLastWriteTime) i pozwolenie, żeby środowisko ASP.NET zajęło się resztą.

Rozwiązanie bardziej eleganckie: HttpRuntime.UnloadAppDomain(). Wówczas środowisko kulturalnie kończy swoje działanie, a aplikacja jest uruchamiana ponownie przy następnym żądaniu. Wady? Niestety, wymaga full trust.

przez jakubin | 2 komentarzy

Filed under: ,

Grupa Łódź ruszyła!

Z wielką radością informuję, że już niedługo zakończy się niechlubny okres, kiedy to drugie trzecie miasto w Polsce nie miało swojej społeczności entuzjastów technologii Microsoft. Już w połowie grudnia rozpocznie swoją działalność od dawna oczekiwana Łódzka Grupa Profesjonalistów IT & .NET!

Interesują nas wszelkie zagadnienia związane z technologiami Microsoft - od szeroko pojętej tematyki IT Pro, przez SQL Server, aż po tworzenie oprogramowania w oparciu o platformę .NET.

Pierwsze spotkanie odbędzie się 16 grudnia 2008 - Wydział Chemii Uniwersytetu Łódzkiego w Łodzi przy ul. Tamka 12 - więcej szczegółów i agenda spotkania już za kilka dni.

Wszystkie osoby i firmy zainteresowane życiem grupy lub chcące aktywnie włączyć się w jej działalność, zapraszamy na stronę:
http://www.grupa-lodz.org

lub zachęcam do kontaktu mailowego: kontakt [monkey] grupa-lodz [dot] org.

przez jakubin | 9 komentarzy

?
?

przez jakubin | 7 komentarzy

Wnioski po MTS 2008

Microsoft Technology Summit 2008 zakończył się. Niestety, co bardziej aktywni blogerzy już dawno zdążyli opisać swoje wrażenia, więc moja relacja byłaby równie gorąca i na czasie co otwierająca MTS sesja o historii wstążki w Office 2007. Dlatego nie napiszę kolejnej relacji, a kilka wniosków, jakie wyciągnąłem z tej konferencji.

Jak w przyszłości wybierać sesje?

  1. Sprawdzeni prezenterzy.
    Są takie osoby, na których sesje można pójść na ślepo. W moim przypadku byli to Tadeusz Golonka, Bartosz Pampuch i Tomasz Kopacz. Żaden z nich mnie nie zawiódł. Po MTS do listy doszło jeszcze jedno nazwisko - Bartłomiej Zass.
  2. Wybierać tematy, o których nic lub prawie nic nie wiesz.
    Prawda znana od lat... Tylko nie wiedzieć czemu, zawsze przy rejestracji o niej zapominałem i wybierałem znane mi tematy. Bo niby poziom 400, więc pewnie dowiem się wielu nowych szczegółów. Na MTSie trochę przypadkowo wybrałem się na sesję o programowaniu mobilnym. Kiedyś trochę bawiłem się Compact Framework 3.5, ale krótko i bez robienia jakiś zaawansowanych rzeczy. I ta właśnie sesja okazała się być idealna dla mnie - Bartłomiej Zass bardzo ciekawie zaprezentował, jak wielkie możliwości daje CF 3.5 + Visual Studio 2008 w zakresie programowania na platformę Windows Mobile. Nie zdawałem sobie sprawy, że takie czynności jak odczytywanie pozycji przez GPS, interakcja z telefonem (SMSy, telefony), czy zaawansowana emulacja urządzenia (utrata zasięgu, rozładowanie baterii) są tak proste w realizacji.
  3. Nie sugerować się zbytnio poziomem.
    Niestety, nie zawsze sesje bywają rzeczywiście na takim poziomie, jaki jest deklarowany. W przypadku wyboru prezentacji "Wydajne aplikacje ASP.NET w świecie Web 2.0" kierowałem się poziomem 400 i... bardzo się zawiodłem. Na początku dowiedzieliśmy się, jak uruchomić na stronie ASP.NET AJAX ("a teraz wstawiamy kontrolkę UpdatePanel"), co to jest LINQ i LINQ2SQL oraz jak zrobić wydajne stronicowanie. Jedynym przykładem na poziomie był Single Sign On dla kilku aplikacji webowych (na przykładzie własnej witryny ASP.NET i Community Servera).
  4. Nie sugerować się zbytnio tematem.
    Czasami bywa tak, że nie do końca precyzyjnie sformułowany temat może błędnie zachęcić lub zniechęcić do zapisania się na sesję. W moim wypadku o mały włos zrezygnowałbym ze świetnej sesji Bartka Pampucha pt. ".NET bez wizardów – sposoby tworzenia i dynamicznego aktywowania komponentów w aplikacjach", gdyż po przeczytaniu tematu, nie za bardzo wiedziałem o czym będzie sesja.
  5. Nie sugerować się zbytnio firmą prezentera.
    Często gdy prelegent pracuje w dużej, renomowanej firmie, to wydaje nam się, że wiedza, jaką może nam przekazać będzie wyjątkowa. Zwłaszcza kiedy tą firmą jest... Agencja Bezpieczeństwa Wewnętrznego, a tematyką "Bezpieczeństwo serwisów WWW – praktyczne uwagi o implementacji zaleceń DBTI ABW w ASP.NET". Trochę naiwnie uznałem, iż ABW posiada jakąś tajemną wiedzę z zakresu zabezpieczeń i przekaże ją nam podczas sesji. Naiwny ja! Sama prezentacja była jak najbardziej w porządku, ale rewolucji w moim postrzeganiu bezpieczeństwa aplikacji ASP.NET nie spowodowała.

Cechy najlepszych sesji i prezenterów

  1. Nic nie zastąpi doświadczenia. Niestety.
    Cóż, nie zaskoczę nikogo twierdząc, że im większe doświadczenie ma prezenter w danej dziedzinie tym zrobi lepszą prezentację. Opisanie dziesiątek teorii, wykonanych badań i najlepsze zebranie materiału z książek, nie zastąpi własnych przemyśleń i rzeczywistych problemów. Świetnym przykładem jest tutaj Tadeusz Golonka, mający przynajmniej kilkunastoletnie doświadczenie w prowadzeniu projektów i zespołów. Na MTS mówił o zarządzaniu ryzykiem ("Efektywne zarządzanie ryzykiem bez/z Team Foundation Server 2008?"). Temat niby ważny i znany w teorii, ale w praktyce ograniczający się do zrobienia listy ryzyk i umieszczenia jej w czeluściach dokumentacji projektu, której nikt nie czyta. Na początku T. Golonka "pocieszył" publiczność - większość kierowników, z którymi miał kontakt nie zarządza ryzykiem w projektach i nie robi tego, bo nie wie jak. A więc ja, skromny programista, miałem też prawo mieć o tym pojęcie, delikatnie mówiąc, mgliste. Następnie, pokazał w praktyce, jak powinno takie zarządzanie wyglądać.
  2. Zaskoczyć publiczność.
    Nic tak nie zapada w pamięci, jak prezentacja inna niż wszystkie. Po CodeCamp 2008 w Krakowie oraz MTS widzę, że Bartosz Pampuch jest specjalistą w tej dziedzinie. Na CodeCamp była prezentacja o agile... poprowadzona w stylu agile, gdzie zespół (publiczność) decydował o omawianych zagadnieniach. A na MTS był... rzutnik z dotykowym ekranem (WTF?) oraz prezentacja w WPF zamiast PowerPoint. Nie ukrywam, że przez pierwsze 5 minut prezentacji nie słuchałem w ogóle Bartka tylko zastanawiałem się, jakim cudem zwykły nieduży rozwijany ekran rzutnika może reagować na gesty dłoni?! Odpowiedź była prosta - http://www.cs.cmu.edu/~johnny/projects/wii/. W skrócie: reakcja nie na gesty dłoni, tylko na lampkę na podczerwień; nie ekran dotykowy, tylko kamera na podczerwień śledząca do 4 punktów (kontroler Wii) i przesyłająca dane przez bluetooth do komputera + soft zamieniający obraz na ruch myszki. Koszt: ok. 100 zł. Wrażenia publiczności: bezcenne.
  3. Nie tracić czasu.
    Dobrym przykładem "czasopożeraczy" są kawałki kodu pisane na żywo. Ale jak się ich pozbyć? Nie można usunąć z prezentacji przykładów, bo bez nich trudno pokazać, jak działa jakaś technologia. Z drugiej jednak strony, teksty w stylu "a teraz dodajemy referencję do projektu... czekamy aż się pojawi okienko... i nadal czekamy" lub "a teraz kompilujemy... czekamy... aplikacja się uruchamia..." nie pobudzają specjalnie słuchaczy.
    Bartosz Pampuch świetnie i niezwykle prosto sobie z tym poradził - cały pisany kod nagrał wcześniej jako screencast'y i po prostu puszczał je jako przykłady. Dzięki temu - kod zawsze działał, dłuższe i mniej ciekawe fragmenty obejrzeliśmy w trybie fast-forward, a ważniejsze momenty powoli. Nie można nie zauważyć w tym momencie świetnego przygotowania Bartka. Każde nagranie miało idealnie dobrane tempo, tak że komentarz dokładnie mieścił się w wyznaczonych ramach. Co więcej, cała sesja była świetnie rozplanowana, nic nie było pominięte lub przegadane. Sam jakiś czas temu miałem przyjemność prowadzić prezentację na spotkaniu WG.NET o podobnej tematyce (AOP i DI). W pewnym momencie trwania sesji Bartka stwierdziłem, że to co mi zajęło pół godziny, on powiedział w 5 minut. Ogólnie - wielkie brawa za tą prezentację!
  4. Wciągnąć publiczność do prezentacji.
    Interakcja z publicznością bywa ryzykowna, nie mniej jednak dobrze poprowadzona potrafi bardzo skutecznie przykuć uwagę. Tak było w przypadku sesji Bartłomieja Zassa pt. "Programowanie mobilne". Pytanie: jak dobrze zilustrować przykład reagowania na zdarzenia smartphona, np. w postaci przychodzącego SMSa? Odpowiedź: napisać aplikację zliczającą SMSy, pokazać ekran komórki na rzutniku i zachęcić publiczność do głosowania "czy obiad był dobry?". Zabawa była ciekawa, zwłaszcza gdy okazało się, że SMSy o treści innej niż przewidziana wyświetlają się jako nowe wiadomości, co można łatwo wykorzystać (wbrew planom prezentera) do zorganizowania chat'u na żywo na wielkim ekranie sali kongresowej :).

przez jakubin | 6 komentarzy

Filed under: ,

opublikowano 31 lipca 2008 01:01
Przygody z Policy Injection Application Block

Policy Injection Application Block w skrócie to biblioteka pozwalająca na wstrzyknięcie kodu opakowującego wywołania metod. Dzięki temu za pomocą jednego atrybutu lub odpowiedniego wpisu w pliku konfiguracyjnym, możemy "nakazać", aby np. czas wykonania metody był mierzony i logowany. Oczywiście, różnych takich zastosowań możemy wyobrazić sobie bardzo wiele. Tyleż samo widać korzyści z zastosowania tego podejścia. Programowanie aspektowe (inaczej AOP - aspect oriented programming), o którym właśnie mowa, pozwala ograniczyć ilość powtarzalnego kodu, tak aby programista mógł skoncentrować na implementowanej funkcjonalności, a architekt mógł zaimplementować raz a dobrze kluczowe kwestie (logowanie, obsługa wyjątków, transakcje, itp.).

Pierwsze zauroczenie...

Moją przygodę na poważnie z PIAB zacząłem jakiś czas temu, kiedy postanowiłem wykorzystać ten blok w jednym z projektów. Spośród istniejących rozwiązań wybrałem właśnie blok z Enterprise Library, kierując się przy tym po części instynktem, a po części zaufaniem do technologii sygnowanych przez samą grupę Patterns & Practices z Microsoft. Początki były bardzo miłe - zalety programowania aspektowego są oczywiste. Ponadto, wraz z PIAB dostajemy zestaw gotowych aspektów (a właściwie "call handlers"), integrujących blok z resztą biblioteki. Tak więc wstrzyknięcie kodu do logowania, obsługi wyjątków, walidacji, autoryzacji, itd. uzyskujemy dodając w najprostszym przypadku jeden atrybut.

Wykorzystanie samego PIAB jest proste i nie wymaga żadnych skomplikowanych czynności. Jeżeli planujemy dodawać aspekty tylko poprzez atrybuty, to nawet nie potrzebujemy dodatkowych wpisów w pliku konfiguracyjnym. Wystarczy tylko w specjalny sposób "opakować" obiekt, w który chcemy wstrzyknąć dodatkową funkcjonalność. Oto prosty przykład pokazujący, w jaki sposób uzyskać walidację parametrów metody:

public interface ILogic
{
    [ValidationCallHandler]
    void SayMyName([NotNullValidator]string name);
}

public class Logic : ILogic
{
    public void SayMyName(string name)
    {
        Console.WriteLine(name);
    }
}
Listing 1. Przykład zastosowania PIAB.

Atrybut ValidationCallHandlerAttribute powoduje, iż przed uruchomieniem właściwego kodu metody uruchamiana jest walidacja parametrów. Atrybut NotNullValidator pochodzi już z Validation Application Block, a jego działanie jest chyba oczywiste.

Aby PIAB zadziałało potrzeba jeszcze opakować obiekt klasy Logic:

ILogic logic = PolicyInjection.Wrap<ILogic>(new Logic());

Oczywiście teraz po wywołaniu kodu:

logic.SayMyName(null);

dostaniemy wyjątek ArgumentValidationException, choć nigdzie jawnie nie dokonywaliśmy sprawdzania ani wyrzucania wyjątku. Wygodne, prawda?

...a po upływie miesiąca miodowego...

Zawsze po miłych początkach, nadchodzą pierwsze rozczarowania. Tak samo było, niestety, z moim "romansem" z PIAB. Zanim jednak przejdę do rzeczy, warto zajrzeć pod maskę i zobaczyć jak PIAB działa.

Otóż, podstawowym wymaganiem, by móc wstrzykiwać polityki do klasy jest aby:

  • implementowała ona interfejs przez który będziemy się do niej odwoływać lub
  • dziedziczyła po MarshalByRefObject.

Wymagania te wynikają stad, iż PIAB wewnętrznie wykorzystuje mechanizmy z .NET Remoting i magię CLR, która pozwala utworzyć transparentne proxy dla interfejsów lub właśnie typów dziedziczących po MarshalByRefObject. Owe transparentne proxy przekazują wywołanie do rzeczywistego proxy, którego zadaniem jest utworzenie ciągu odpowiednich call handlers (aspektów). Ostatnim na liście będzie zawsze kod dokonujący rzeczywistego wywołania docelowej metody. Poniższy diagram UML prezentuje jak to w uproszczeniu wygląda:

piab 
Diagram 1. Schematyczna struktura klas przy przechwytywaniu wywołań metod przez PIAB.

Jak widać ciąg wywołań będzie następujący:

  1. Użytkownik wywołuje metodę na transparent proxy.
  2. Dane o wywołaniu zostają zebrane (wartości argumentów, metoda, itp.) i trafiają do metody Invoke() klasy InterceptingRealProxy.
  3. Informacje o wywołaniu oraz delegat wywołujący następny call handler trafiają do pierwszego handlera. Handler może opakować swoją funkcjonalnością dalsze wywołanie.
  4. Wywołanie trafia stopniowo do wszystkich handlerów, aż na samym końcu pojawia się kod, który przez refleksję wywołuje docelową metodę. Wygląda on mniej więcej tak:
       1: try
       2: {
       3:     object returnValue = call.MethodBase.Invoke(/*..*/);
       4:     return /*...*/; //obiekt z informacją o powodzeniu i rezultatem
       5: }
       6: catch (TargetInvocationException ex)
       7: {
       8:     return /*..*/; //obiekt z informacją o wyjątku ex.InnerException
       9: }
    Listing 2. Wywołanie metody docelowej przez infrastrukturę PIAB.

Warto zwrócić uwagę na linijki 6-9, gdyż sygnalizują one mój pierwszy, na razie drobny problem.

Wyjątek wyrzucany dwa razy

Oto pierwsza niespodzianka - wywołując poniższy kod z Visual Studio:

public class Logic : MarshalByRefObject
{
    [ValidationCallHandler]
    public void SayMyName([NotNullValidator]string name)
    {
        Console.WriteLine(PrepareName(name));
    }

    private string PrepareName(string name)
    {
        throw new NotImplementedException();
    }
}

public class Program
{
    private static void Main(string[] argv)
    {
        var logic = PolicyInjection.Create<Logic>();
        logic.SayMyName("Kuba");
    }
}
Listing 3. Podróż wyjątku.

dostajemy następujący efekt:

image

Wygląda to, jakby wyjątek powstał gdzieś pomiędzy wywołaniem, a samym ciałem metody (co jest poniekąd prawdą). Spodziewalibyśmy się okienka dokładnie w miejscu wyrzucenia, czyli w metodzie PrepareName(). Na szczęście jest jeszcze stack trace, który zawiera cały stos wraz z miejscem postania wyjątku. Niby nic, ale każdy mniej doświadczony programista zgłosi się do nas, uznając, że jest to błąd w architekturze.

Z czego takie zachowanie wynika? Warto prześledzieć, co dzieje się z wyjątkiem:

  1. Wyjątek jest rzucany w metodzie PrepareName().
  2. Jako, że metoda SayMyName została wywołana za pomocą infrastruktury PIAB (poprzez refleksję - MethodBase.Invoke()), więc zostanie on opakowany przez TargetInvocationException. Ten zostanie przechwycony (listing 2, linijki 6-9), oryginalny wyjątek zostanie wyłuskany i przekazany jako element wyniku działania funkcji do wcześniejszych handlerów.
  3. Gdy wróci przez wszystkie handlery, to zostaje ponownie wyrzucony.

Stąd właśnie inne miejsce wyrzucenia wyjątku, wskazywane przez okienko "unhandled exception".

Prawdziwe problemy - środowisko hostowane, czyli partial trust...

Swojego czasu musiałem umieścić aplikację na jednym z większych polskich serwisów prowadzących hosting Windows. I tutaj zaczęły się prawdziwe schody. Problem polegał na tym, że w takich środowiskach poziom zabezpieczeń prawie nigdy nie jest ustawiony na Full Trust, a Enterprise Library nie zawsze jest dostępny z GAC (tak było w moim przypadku). Od razu zaznaczam, że problemem nie był fakt, że dopiero wersja 4.0 ma atrybut AllowPartiallyTrustedCallersAttribute (tzw. APTCA), a więc wersje wcześniejsze trzeba przekompilować bez podpisu cyfrowego, aby w ogóle się uruchomiły.

Problem polega na tym, że PIAB od środka wykorzystuje własne rzeczywiste proxy - InterceptingRealProxy, które dziedziczy po System.Runtime.Remoting.Proxies.RealProxy, która to klasa ma jako jeden z atrybutów:

[SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]

Oznacza to, że aby dziedziczyć po RealProxy, assembly musi mieć uprawnienie do wpinania się do infrastruktury .NET Remoting. Jak nie trudno zgadnąć, we współdzielonym środowisku hostowanym zwykle takiego uprawnienia nie dostaniemy, a więc próba utworzenia proxy skończy się... SecurityException. Możemy poprosić administratora o nadanie tego uprawnienia, choć jest mało prawdopodobne, że się zgodzi.

Sytuacja na szczęście nie jest patowa - istnieje jeszcze jedno wyjście. PIAB pozwala na napisanie i podpięcie własnego Policy Injectora, który już z infrastruktury .NET Remoting korzystać nie musi. Na takie wyjście ja się zdecydowałem - postanowiłem napisać mechanizm, który będzie dynamicznie generował obiekty proxy implementujące wskazany interfejs i dalej wywołujące całą infrastrukturę Policy Injection (oczywiście takie podejście wyklucza wstrzykiwanie polityk do MarshalByRefObject). I po kilka dniach wspaniałej przygody z dynamicznym generowaniem kodu IL, z której wrażenia można porównać jedynie do czyszczenia zatkanych rur kanalizacyjnych, uzyskałem w miarę zadowalające rozwiązanie.

Pozwolę sobie nie przedstawiać dalszych szczegółów technicznych, gdyż nikt i tak by tego nie przeczytał, a najwięksi śmiałkowie pewnie zasnęliby w trakcie. Niemniej jednak, gdy moje rozwiązanie dopracuję (ma jeszcze kilka znanych mi błędów), to postaram się je opublikować, aby więcej osób mogło skorzystać z moich walk i nie musiało samemu przechodzić przez to piekiełko :).

Podsumowanie

Policy Injection Application Block to całkiem interesujące i wygodnie rozwiązanie, niemniej jednak, strategia przechwytywania wywołań wybrana przez twórców (infrastruktura .NET Remoting) może powodować problemy przy pewnych specyficznych sytuacjach, jak np. wdrażanie w środowisku o mniejszych uprawnieniach. Warto zdawać sobie z tego sprawę przy wyborze technologii wspierającej AOP podczas prac nad architekturą aplikacji.

przez jakubin | 4 komentarzy

opublikowano 16 lipca 2008 00:39
Transakcyjny mailing

W czym problem?

Wysłanie wiadomości e-mail w .NET jest dziecinnie proste:

MailMessage message = new MailMessage(
    "from@server.com", "to@server.com", "Temat", "Treść");
SmtpClient smtp = new SmtpClient();
smtp.Send(message);

Powyższy przykład jest bardzo krótki, choć i tak został napisany niezwykle rozwlekle - wersja zminimalizowana zajęłaby 1 linijkę (w obu przypadkach ustawienia serwera pocztowego znajdują się w pliku konfiguracyjnym). Właściwie ten kawałek kodu powinien wystarczyć nam do szczęścia, ale czasami zdarzają się bardziej skomplikowane przypadki.

Otóż, ostatnio pisałem kod, w którym kilka metod było wykonywanych w jednej transakcji. Jedna z metod wysyłała wiadomość e-mail z powiadomieniem o powodzeniu operacji. Problem pojawiał się, gdy wykonanie kolejnej się nie powiodło. Wówczas, transakcja nie była zatwierdzana - zmiany nie zostały zapisane do bazy, ale wiadomość o powodzeniu była wysyłana! Poniżej zamieściłem przykład takich metod:

public static void CreateUser(string email)
{
    //rejestracja konta użytkownika
    //...
    
    //wysłanie powiadomienia o rejestracji
    MailingHelper.Send(
        new MailMessage("admin@serwis.com",email,
            "Witaj na portalu!", 
            "Twoje konto zostało zarejestrowane."));
}

public static void GiveSiteAccess(string email)
{
    //nadanie uprawnień do serwisu WWW
    //...
}

Oraz ich wywołanie w ramach transakcji:

using (var ts = new TransactionScope())
{
    CreateUser("user@mail.com");
    GiveSiteAccess("user@mail.com");
    
    ts.Complete();
}

Jak widać, gdy wywołanie metody GiveSiteAccess się nie powiedzie, to i tak do użytkownika trafi wiadomość e-mail o sukcesie rejestracji, choć ta de facto nie nastąpiła.

Zadałem, więc sobie pytanie, czy nie da się jakoś podpiąć procesu wysyłania maili do bieżącej transakcji? Okazuje się, że tak i to bardzo łatwo. Wystarczy napisać własny menadżer zasobów i dołączyć go do transakcji.

Rozwiązanie - własny menadżer zasobów

Do informacji o bieżącej transakcji dostajemy się poprzez statyczną właściwość Current klasy System.Transactions.Transaction (w assembly System.Transactions.dll). Tam dostępne są metody, które pozwalają "dopisać się" do bieżącej transakcji:

  • EnlistVolatile - dołącza do transakcji menadżera zasobów ulotnych (zasoby ulotne to np. dane w pamięci; taki menadżer nie obsługuje odtwarzania stanu po błędzie),
  • EnlistPromotableSinglePhase - dołącza do transakcji menadżera zasobów, który obsługuje tryb promotable single phase enlistment,
  • EnlistDurable - dołącza do transakcji menadżera zasobów trwałych (zasoby trwałe to np. dane na dysku twardym; taki menadżer posiada obsługę odtwarzania stanu po błędzie).

Nas będzie interesować pierwsza metoda, gdyż wiadomość e-mail to zasób ulotny. Menadżer zasobów ulotnych w najprostszej postaci to klasa implementująca interfejs IEnlistmentNotification. Nasz menadżer będzie wyglądał następująco:

public class MailSender : IEnlistmentNotification
{
    private readonly MailMessage message;

    public MailSender(MailMessage message)
    {
        this.message = message;
    }

    #region IEnlistmentNotification Members

    public void Commit(Enlistment enlistment)
    {
        //powoduje faktyczne wysłanie wiadomości
        MailingHelper.SendImmediately(message);
        enlistment.Done(); //menadżer zgłasza zakończenie swojej pracy
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        //menadżer potwierdza zakończenie przygotowań
        preparingEnlistment.Prepared(); 
    }

    public void Rollback(Enlistment enlistment)
    {
        enlistment.Done(); //menadżer potwierdza wycofanie transakcji
    }

    #endregion
}

Metody interfejsu IEnlistmentNotification będą wykonywane zgodnie z protokołem dwufazowego zatwierdzania (two-phase commit). W dużym skrócie, proces ten składa się z dwóch faz: fazy przygotowania i fazy zatwierdzania. Menadżer transakcji najpierw żąda przygotowania się menadżerów zasobów - metoda Prepare. Każdy menadżer może zgłosić gotowość (wywołanie preparingEnlistment.Prepared()) lub wymusić wycofanie transakcji (wywołanie preparingEnlistment.ForceRollback()). Jeżeli wszystkie menadżery zgłoszą gotować, to transakcja jest zatwierdzana - metoda Commit. W przeciwnym razie wszystkie menadżery dostają polecenie wycofania transakcji - metoda Rollback. Natomiast, gdy menadżer transakcji straci łączność z którymś z menadżerów zasobów, to wywoływana jest metoda InDoubt.

Dołączenie menadżera zasobów do transakcji odbywa się następująco:

Transaction.Current.EnlistVolatile(new MailSender(message), EnlistmentOptions.None);

Ponieważ cała magia jest już znana, oto kod klasy pomocniczej MailingHelper:

public static class MailingHelper
{
    public static void Send(MailMessage message)
    {
        if (Transaction.Current != null)
        {
            Transaction.Current.EnlistVolatile(
                new MailSender(message), EnlistmentOptions.None);
        }
        else
            SendImmediately(message);
    }

    public static void SendImmediately(MailMessage message)
    {
        var smtp = new SmtpClient();
        smtp.EnableSsl = true;
        smtp.Send(message);
    }
}

Jak widać, metoda SendImmediately wysyła wiadomość natychmiast, podczas gdy metoda Send podpina wysłanie wiadomości pod bieżącą transakcję (jeżeli taka istnieje; w przeciwnym razie wysyła maila od razu). Efekt jest taki, jaki chcieliśmy osiągnąć - poczta jest wysyłana dopiero w momencie zatwierdzenia transakcji.

Kilka słów wyjaśnienia

Przedstawione rozwiązanie, wbrew tytułowi, nie jest receptą na zbudowanie w pełni transakcyjnego mailingu. Tutaj tylko osiągamy efekt opóźnienia wysłania wiadomości e-mail do momentu zatwierdzenia transakcji. Ponadto, kod został uproszczony dla zwiększenia czytelności.

Bardziej dociekliwych zapraszam do:

  1. Dokumentacja klasy Transaction.
  2. Dokumentacja klasy TransactionScope.
  3. Dokumentacja interfejsu IEnlistmentNotification.
  4. Artykuł o implementacji własnego menadżera zasobów.

przez jakubin | 7 komentarzy

Filed under:

opublikowano 18 czerwca 2008 08:25
Co z tym DataContext?

Chyba trudno znaleźć programistę, który po dłuższej pracy z LINQ-to-SQL nie uznałby tej technologii za przełomową pod względem wygody i szybkości tworzenia aplikacji w porównaniu do tego, co wcześniej oferował sam .NET: DataSets oraz czyste DbConnections i spółka. Jednakże używanie tego rozwiązania na dłużą metę nie jest wolne od kilku wyzwań (i bardzo dobrze).

W Linq2Sql klasą dającą dostęp do bazy danych jest DataContext (lub dziedzicząca po nim, dla silnie typowanych kontekstów). Jednakże nie trudno zadać sobie pytanie - jak skutecznie zarządzać obiektem DataContext, aby zapewnić maksimum wygody i wydajności. W tym wpisie chciałbym właśnie przedstawić rozwiązanie, którego ja używam i które do tej pory u mnie się sprawdza.

Od razu zaznaczam, że nie chodzi mi o kwestie czasu życia samego kontekstu (czy jeden na metodę, wątek, aplikację, itd.), bo zgodnie z MSDN:

In general, a DataContext instance is designed to last for one "unit of work" however your application defines that term. A DataContext is lightweight and is not expensive to create. A typical LINQ to SQL application creates DataContext instances at method scope or as a member of short-lived classes that represent a logical set of related database operations.

Ja przyjąłem zasadę jeden DataContext per metoda.

Problemy z DataContext

Z początku wydawać by się mogło, że goły DataContext nie stwarza żadnych wyzwań. W końcu piszemy:

using (var dc = new MyDbDataContext())
{
    //operacje na bazie
}

i wszystko pięknie działa. Problemy widać już lepiej w poniższym kodzie:

   1: public class App
   2: {
   3:     public static Owner GetOwner(int ownerId)
   4:     {
   5:         using (var dataContext = new MyDbDataContext())
   6:         {
   7:             var loadOptions = new DataLoadOptions();
   8:             loadOptions.LoadWith<Dog>(d => d.Breed);
   9:             dataContext.LoadOptions = loadOptions;
  10:  
  11:             return (from o in dataContext.Owners
  12:                     where o.OwnerId == ownerId
  13:                     select o)
  14:                 .FirstOrDefault();
  15:         }
  16:     }
  17:  
  18:     public static bool CanBeBreeder(int ownerId, string breed)
  19:     {
  20:         using (var dataContext = new MyDbDataContext())
  21:         {
  22:             var loadOptions = new DataLoadOptions();
  23:             loadOptions.LoadWith<Dog>(d => d.Breed);
  24:             dataContext.LoadOptions = loadOptions;
  25:  
  26:             return (from d in dataContext.Dogs
  27:                     where d.OwnerId == ownerId && d.Breed.Name == breed
  28:                     select d).Count() >= 2;
  29:         }
  30:     }
  31:  
  32:     public static void MakeBreeder(int ownerId)
  33:     {
  34:         using (var dataContext = new MyDbDataContext())
  35:         {
  36:             var loadOptions = new DataLoadOptions();
  37:             loadOptions.LoadWith<Dog>(d => d.Breed);
  38:             loadOptions.LoadWith<Dog>(d => d.Owner);
  39:             dataContext.LoadOptions = loadOptions;
  40:             
  41:             var owner = dataContext.Owners.First(o => o.OwnerId == ownerId);
  42:             owner.IsBreeder = true;
  43:             dataContext.SubmitChanges();
  44:         }
  45:     }
  46:  
  47:     static void Main(string[] argv)
  48:     {
  49:         int ownerId = 1;
  50:         using (var ts = new TransactionScope())
  51:         {
  52:             var owner = GetOwner(ownerId);
  53:             if (!owner.IsBreeder)
  54:             {
  55:                 if (CanBeBreeder(ownerId, "Labrador"))
  56:                     MakeBreeder(ownerId);
  57:             }
  58:             ts.Complete();
  59:         }
  60:     }
  61: }

Otóż, powyższy kod:

  • wykorzystuje 3 niezależne połączenia do tej samej bazy (z tym samym ConnectionString),
  • tworzy ciężką i kosztowną transakcję rozproszoną (wymaga włączonej usługi Distibuted Transaction Coordinator),
  • w każdej metodzie definiuje takie same LoadOptions dla DataContext (ale czasami rozszerza ten zbiór o dodatkowe ustawienia),

Innymi słowy - jest długi, mało wydajny i trudny do utrzymania. Od razu widać 2 problemy, z jakimi przyjdzie nam się zmagać:

  1. Jak zarządać połączeniem do bazy danych, aby ograniczyć ich ilość do minimum (a przy okazji unikać transakcji rozproszonych)?
    Idealnym rozwiązaniem byłoby, aby wszystkie operacje w ramach jednego wątku i takiego samego DataContextu odbywały się na tym jednym połączeniu.
  2. Jak zdefiniować domyślne Load Options dla DataContextu, które można rozszerzać?
    Właśnie kwestia rozszerzalności jest najbardziej problematyczna. O ile silnie typowany DataContext jest klasą partial i ma metodę partial OnCreated, w której możemy utworzyć domyślne DataLoadOptions, o tyle już po przypisaniu tych opcji do kontekstu, nie ma możliwości ich zmiany (bez tworzenia DataLoadOptions od nowa).

Propozycja rozwiązania

Moja propozycja opiera się na opakowaniu DataContext przez inną klasę. Zadaniem tej klasy jest zarządzanie połączeniem i Load Options oraz tworzenie odpowiednio skonfigurowanego DataContext. Oto jej kod:

/// <summary>
/// Creates a "DbConnection scope", DataContext and manages its LoadOptions.
/// </summary>
public class MyDbDatabase : IDisposable
{
    #region Private fields
 
    [ThreadStatic]
    private static MyDbDatabase CurrentContext;
    private readonly DataLoadOptionsBuilder loadOptionsBuilder = new DataLoadOptionsBuilder();
    private readonly ConnectionMode mode;
    private readonly MyDbDatabase parent;
    private DbConnection connection;
    private MyDbDataContext context;
    private bool createdConnection;
 
    #endregion
 
    #region Properties
 
    /// <summary>
    /// Gets the current connection.
    /// </summary>
    /// <value>The connection.</value>
    public DbConnection Connection
    {
        get
        {
            if (connection == null)
            {
                if (mode == ConnectionMode.UseExisting)
                {