19/02/2012

RavenDB (cz. 4) - zapytania 2

Home

Ten post to kontynuacja poprzedniego postu dotyczącego zapytań w Raven DB.

Contains == Equals !!!

Jedną z funkcjonalności jaką chciałem zaimplementować w swoim programie było wyszukiwanie wyrażeń/tłumaczeń zawierających podany ciąg znaków. Brzmi prosto ale nie obyło się bez problemów. Otóż okazało się, że Raven DB traktuje wywołanie metody String.Contains jako String.Equals, chyba że do poszukiwanego ciągu znaków dodamy gwiazdki. Na przykład zamiast s.Contains("kot") użyjemy s.Contains("kot*"), s.Contains("*kot") lub s.Contains("*kot*") w zależności czy poszukiwane wyrażenie ma znajdować się na początku, na końcu lub w środku.
string textToFind = String.Format("*{0}*", textToFind);

var res = 
 from ex in session.Query<ExpressionEntity>()
 where ex.Expression!= null && ex.Expression.Contains(textToFind) || ex.Translations.Any(t => t.Translation != null && t.Translation.Contains(textToFind))
 orderby ex.Expression
 select ex
Pozostaje jeden problem, którego niestety nie udało mi się rozwiązać. Ten kod nie zadziała jeśli szukany tekst zawiera białe znaki. Załóżmy, że na liście naszych wyrażenie mamy wyrażenie Ala ma kota. Jeśli spróbujemy wyszukać "*Ala*", "*ma*", "*kota*" otrzymamy poprawny wynik. Jeśli natomiast spróbujemy wyszukać całe wyrażenie "*Ala ma kota*" zapytanie zwróci nam pustą listę. Co dziwniejsze jeśli nie użyjemy gwiazdek czyli zlecimy wyszukanie "Ala ma kota" wyrażenie zostanie znalezione.

Ale co, jeśli szukam podwyrażenia "Ala ma" lub "ma kota"? Nie jest to duże pocieszenie ale Raven DB poradzi sobie z "Ala ma*" (z "*ma kota" już nie). Moim zdaniem sugeruje to, że wewnętrznie Raven DB do wyszukiwania używa jakiejś struktury opartej o prefixy.

Małe ostrzeżenie

W jednym z zapytań chciałem użyć metody String.Equals ale zakończyło się to wyjątkiem NullReferenceException. Sądzę, że jest to związany z użyciem metod statycznych bo kiedy użyłem metody instancyjnej np.: ex.Expression.Equals(textToFind, StringComparison.InvariantCultureIgnoreCase) obyło się bez błędów.

Stale Data

Jedną z głównych idei przyświecających twórcom Raven DB było zapewnić aby pytający o dane jak najszybciej uzyskał odpowiedź nawet jeśli oznaczałoby to zwrócenie nieaktualnych, przestarzałych danych. Używając Raven DB trzeba być na to przygotowanym nawet pracując z małą ilością danych. W moim przypadku baza danych zawiera ok. 600 dokumentów, nie całe 0.5 MB po wyeksportowaniu do pliku XML i obserwuję ten efekt.

Na przykład na początku moja aplikacja wyświetlała liczbę wyrażeń obok etykiety z nazwą języka. Jeśli jednak użytkownik importował do aplikacji wiele wyrażeń to operacja zapisywania danych, odświeżania indeksów itd. po stronie bazy danych trwała na tyle długo, że wyliczona liczba wyrażeń nie zgadzała się ze stanem faktycznym.
  • Użytkownik rozpoczyna import wyrażeń.
  • Zakończenie importu z perspektywy aplikacji/użytkownika.
  • Obliczenie liczby wyrażeń w poszczególnych językach.
  • ...
  • Faktyczne zakończenie importu po stronie bazy danych.
W tej chwili do problemu podchodzę tak, że liczbę słów w danym języku wyświetlam na życzenie. Jeśli takie zachowanie Raven DB jednak nam przeszkadza to należy explicite powiedzieć, że nie śpieszy się nam i możemy poczekać na aktualne dane np.:

var res = 
 from ex in session.Query<ExpressionEntity>().Customize(a => a.WaitForNonStaleResults())
 select ex;

Lucene

Raven DB jako silnika indeksujący używa technologii Lucene .NET. Co ciekawe umożliwia zdefiniowanie zapytań w składni jaką posługuje się ten silnik. Bezpośrednie użycie Lucene może wyglądać tak:
var res = session.Advanced.LuceneQuery<ExpressionEntity>().Where(luceneQuery);
Gdzie luceneQuery to po prostu ciąg znaków zawierający zapytanie w składni zrozumiałej dla Lucene. Może to się przydać kiedy będziemy chcieli zadać zapytanie nie obsługiwane przez "LINQ to Raven DB".

Indeksy

Jeszcze parę słów o indeksach. Termin ten przewinął sie już kilka razy. Pracując z Raven DB trzeba sobie przede wszystkim uświadomić, że tutaj indeksy różnią się od indeksów używanych w bazach relacyjnych. Moim zdaniem dwie różnice są fundamentalne. Po pierwsze Raven DB indeksuje zapytania, a nie dane. Indeks definiujemy definiując zapytanie, a nie wskazując na przykład atrybut dokumentu, który ma zostać zaindeksowany.

Po drugie Raven DB używa indeksów przy wykonywaniu każdego zapytania nawet jeśli taki indeks nie został zdefiniowany. W takiej sytuacji tworzony jest indeks dynamiczny. Druga grupa indeksów to indeksy statyczne, zdefiniowane explicite przez użytkownika na podstawie jego wiedzy o zadawanych pytaniach. Tymczasowe indeksy dynamiczne mogą zostać zamienione na stałe jeśli Raven DB zauważy, że taki indeks jest często używany.

Podsumowanie

O ile sposób realizacji podstawowych operacji dodaj/usuń/zmodyfikuj na dokumentach bardzo chwaliłem ze względu na jego prostotę i niski koszt wejścia to o raportowaniu/zapytaniach nie mogę już tego powiedzieć. Z jednej strony nie jest to żaden rocket science ale z drugiej nie jest to już "bułka z masłem" i wymaga znajomości rożnych sztuczek i specyfiki Raven DB. Jeśli uwzględnić, że zapytania jakich użyłem były bardzo proste, a pomimo to sprawiły mi sporo kłopotów to obawiam się, chociaż są to tylko moje przypuszczenia, że realizacja skomplikowanych raportów może być w Raven DB bardzo nietrywialna.

Podsumujmy co już umiemy:
  • Osadzić Raven DB w aplikacji hostującej.
  • Zainicjować Raven DB.
  • Skonfigurować dostęp do Raven Studio i API REST'owego.
  • Tworzyć obiekty POCO jakie mogą zostać umieszczone w Raven DB.
  • Dodawać/usuwać/modyfikować dokumenty.
  • Zadawać proste i te trochę bardziej skomplikowane zapytania.
  • Utworzyć indeks.
  • Skorzystać z algorytmu Map/Reduce.
  • Skorzystać z zapytań Lucene.
  • Wymusić zwrócenie przez zapytanie aktualnych danych.

13/02/2012

Problem z linkerem

Home

Na blogu nie poruszałem jeszcze tematyki C++ ale skoro nadarzyła się okazja to czemu nie. Otóż, przez mocno już zirytowanego programistę, zostałem poproszony o pomoc w rozwiązanie problemu z kompilacją programu napisanego w C++.

Rzeczony programista chciał w swojej aplikacji użyć znalezionej w sieci biblioteki kryptograficznej. W ustawieniach projektu wskazał, więc lokalizację pliku lib z bibliotekę oraz lokalizację potrzebnych plików nagłówkowych. Niestety, pomimo wielokrotnych prób, sprawdzaniu ustawień, rwania włosów z głowy itd. próba kompilacji zawsze kończyła się długą listą błędów.



Rozwiązanie okazało się tyle proste, co nieintuicyjne i trudne do znalezienia jeśli sie o tym nie wie. Otóż biblioteka kryptograficzna została skompilowana z inną wartością parametru Runtime Library tj. Multi-threaded Debug (/MTd) niż aplikacja tj. Multi-threaded Debug DLL (/MDd). Po zmianie ustawień parametru Runtime Library dla aplikacji na wartość Multi-threaded Debug (/MTd) proces kompilacji wreszcie zakończył się sukcesem.


12/02/2012

Problem z AssemblyResolve

Home

Niedawno miałem okazję pracować z aplikacją, w której zaimplementowano obsługę zdarzenia AppDomain.AssemblyResolve. Zdarzenie to pozwala załadować assembly jeśli standardowy mechanizm platformy .NET nie poradzi sobie z tym zadaniem. Aplikacja działała poprawnie aż do migracji na platformę .NET 4.0 Czemu? O tym właśnie będzie post. Zwrócę w nim uwagę na dość istotną różnicę pomiędzy platformą .NET 4.0, a jej wcześniejszymi wersjami jeśli chodzi o wspomniane zdarzenie. Różnica ta, w określonych warunkach, może napsuć krwi.

Przechodząc do meritum chodzi o to, że począwszy od .NET 4.0 zdarzenie AppDomain.AssemblyResolve generowane jest dla wszystkich assemblies, również tych z zasobami. Poniżej cytat z dokumentacji z MSDN'a.

Beginning with the .NET Framework 4, the ResolveEventHandler event is raised for all assemblies, including resource assemblies. In earlier versions, the event was not raised for resource assemblies. If the operating system is localized, the handler might be called multiple times: once for each culture in the fallback chain.

Oznacza to, że jeśli biblioteka SomeLibrary zawiera zasoby, to zdarzenie AppDomain.AssemblyResolve zostanie wygenerowane dla nie dwa razy. Raz z żądaniem załadowania assembly o nazwie SomeLibrary, a drugi raz z żądaniem załadowania SomeLibrary.resources. Fizycznie oba assembly znajdują się natomiast w dll'ce o nazwie SomeLibrary.dll.

Jeśli obsługa zdarzenia AppDomain.AssemblyResolve zaimplementowana została w uproszczony albo raczej błędy sposób mamy problem. Mam tu na myśli implementację, w której nie przewidziano, że nie uda się znaleźć dll'ki o nazwie zgodnej z nazwą assembly. W omawianym przypadku wyglądało to mniej więcej tak:
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
 //Determine assembly path based on the configuration
 //...
 
 Assembly asm = Assembly.LoadFrom(path);
 return asm;
}
Łatwo się domyślić, że wywołanie Assembly.LoadFrom(path) dla ścieżki SOME_PATH\SomeLibrary.resources.dll zakończy się błędem bo taka ścieżka nie istnieje. W aplikacji objawi się to wyjątkiem FileNotFoundException przy próbie odczytania czegokolwiek z zasobów. Powinno to zostać zrobione wcześniej ale łatwo to naprawić:
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
 //Determine assembly path based on the configuration
 //...
 
 try
 {
  return Assembly.LoadFrom(path);
 }
 catch(Exception ex)
 {
  //Log exception
  
  return null;
 }
}
Albo w taki sposób, jeśli chcemy obsłużyć tylko scenariusz z zasobami:
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
 //Determine assembly path based on the configuration
 //...
 
 if(args.Name.Contains(".resources,"))
  return null;
  
 Assembly asm = Assembly.LoadFrom(path);
 return asm;
}
Zwrócenie null powoduje, że zostanie użyty standardowy mechanizm wyszukiwania i ładowania assemblies platformy .NET, a ponieważ dll'ka SomeLibrary.dll zostanie załadowana przy pierwszym żądaniu to poradzi sobie on z załadowaniem assembly z zasobami.

07/02/2012

RavenDB (cz. 3) - zapytania

Home

Wstęp

W tej części serii poświęconej Raven DB napiszę o zapytaniach, pobieraniu danych. Temat sam w sobie jest bardzo obszerny i to, co napiszę, to tylko szczyt góry lodowej. Większość tematów po prostu zasygnalizuje, ale sądzę, że dobrze pokaże, jak to wygląda z Raven DB.

Podstawy

Podstawowe zapytania zadajemy w bardzo prosty sposób i robimy to otwierając najpierw sesję pracy z bazą np.:
using (var session = Store.OpenSession())
{
 var res = from ex in session.Query<ExpressionEntity>()
    select ex;
 ...
}
Aby pobrać listę dokumentów użyłem metody Query określając jaki typ dokumentów mnie interesuje. Dla przypomnienia klasa ExpressionEntity reprezentuje wyrażenie i jego tłumaczenia. Jest to ta sama klasa, która wcześniej posłużyła mi do umieszczenia danych w bazie.

Jeszcze jeden przykład. Tym razem pobieram listę dokumentów by na jej podstawie określić listę różnych kategorii, jakie zdefiniowano dla wszystkich wyrażeń.
var res = (from e in session.Query<ExpressionEntity>()
    where e.Category != null
    orderby e.Category
    select e.Category).Distinct();
Jak widać podstawowe zapytania wykonuje się bardzo łatwo.

Stronicowanie

Przy dużej liczbie dokumentów przydatne okaże się stronicowanie. To też nie jest trudne do zrealizowania:
var res = (from ex in session.Query<ExpressionEntity>()
    orderby ex.Expression
    select ex).Skip(index * pageSize).Take(pageSize).ToList();
Użyta w kodzie zmienna index to indeks strony do pobrania (w numerowaniu od zera), a pageSize to oczywiście wielkość strony liczona w liczbie dokumentów. Metoda Skip pozwala więc na przeskoczenie do konkretnej strony, a metoda Take na pobranie takiej liczby dokumentów jaka mieści się na stronie.

Robi się trudniej

Jedną z funkcjonalności, jakie chciałem mieć w swoim programie LanguageTrainer, było zliczanie liczby wyrażeń posiadających tłumaczenie w danym języku. Brzmi prosto, prawda? Pierwsza moja próba wyglądała tak:
 var count = 
 (from ex in session.Query<ExpressionEntity>()
 from t in ex.Translations
 where t.Language == selectedLang && !String.IsNullOrEmpty(t.Translation) 
 select 1).Count();
Zmienna selectedLang zawiera interesujący nas język. Wykonanie takiego zapytania zakończy się wyjątkiem NotSupportedException z komunikatem Method not supported: SelectMany. A więc może coś takiego:
var count =
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && !String.IsNullOrEmpty(t.Translation))
 select 1).Count();
Tym razem zakończy się wyjątkiem z komunikatem Method not supported: IsNullOrEmpty. No cóż IsNullOrEmpty łatwo zastapić zwykłym porównaniem. Spróbujmy więc jeszcze raz:
var count = 
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && t.Translation != null && t.Translation != String.Empty)
 select 1).Count();
To też nie zadziała i znowu zakończy sie błędem, tym razem z komunikatem Node not supported: Constant. Jeszcze jednak próba i w końcu zadziałało:
var count = 
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && t.Translation != null && t.Translation != String.Empty)
 select ex).Count();
Nie jest to skomplikowane ale wymaga znajomości kilku "trików", nie jest do końca intuicyjne.

MapReduce

Do opisanego powyżej problemu można też podejście w Raven DB w inny sposób, a mianowicie stosując algorytm MapReduce. W ten sposób wykonując jedno zapytanie otrzymamy wyniki dla wszystkich języków za jednym razem. W Raven DB robimy to definiując indeks (jeszcze o tym napiszę):
public class TranslationsCounter : AbstractIndexCreationTask<ExpressionEntity, TranslationsCounter.ReduceResult>
{
 public class ReduceResult
 {
  public Languages Lang { get; set; }
  public int Count { get; set; }
 }

 public TranslationsCounter()
 {
  Map = docs => from doc in docs
   from t in doc.Translations
   select new { Lang = t.Language, Count = String.IsNullOrEmpty(t.Translation) ? 0 : 1 };

  Reduce = results => from t in results
   group t by t.Lang
   into g
   select new { Lang = g.Key, Count = g.Sum(x => x.Count) };
 }
}
i zadanie zapytania przy jego użyciu np.:
public IDictionary<Languages,int> CountExpressionsByLanguage()
{
 using (var session = Store.OpenSession())
 {
  var dict = new Dictionary<Languages, int>();

  foreach(var res in session.Query<TranslationsCounter.ReduceResult, TranslationsCounter>())
  {
   dict.Add(res.Lang, res.Count);
  }

  return dict;
 }
}


To jeszcze nie koniec. Do tematu wrócę w kolejnym poście.

22/01/2012

RavenDB (cz. 2) - podstawowe operacje na dokumentach

Home

Wstęp

W drugim poście poświęconym Raven DB opiszę jak wykonywać podstawowe operacje (zapisz/usuń/zmień) na dokumentach. Ale czym jest dokument? Nie wiem jaka jest formalna definicja ale ja na dokumenty patrzę po prostu jak na obiekty zapisane (zserializowane) w formacie JSON. Poniżej przykład takiego dokumentu z mojego pet project, który odpowiada obiektowi klasy reprezentującej wyrażenie i jego tłumaczenia.
{
  "Category": "Geografia",
  "Expression": "wioska",
  "Translations": [
    {
      "Language": "Language1",
      "Translation": "Dorf",
      "GoodAnswers": 0,
      "BadAnswers": 1,
      "WasLastAnswerGood": false
    },
    {
      "Language": "Language2",
      "Translation": "village",
      "GoodAnswers": 1,
      "BadAnswers": 0,
      "WasLastAnswerGood": true
    }
  ]
}
Klasa, której obiekty chcemy wrzucić do Raven DB może być zwykłym POCO z dokładnością do jednej rzeczy. Musi posiadać taką właściwość:
public int Id { get; set; }
To przykład konwencji. Jeśli chcemy zmienić sposób przechowywania identyfikatora, należy skorzystać z DocumentStore.Conventions.FindIdentityProperty. Użyty przeze mnie EmbeddableDocumentStore dziedziczy z DocumentStore, a więc jeśli znudzi mi się Id to zawsze mogę to zmienić.

Jeśli chcemy aby Raven DB ignorował niektóre właściwości w czasie zapisywania obiektu to powinniśmy oznaczyć je atrybutem Newtonsoft.Json.JsonIgnore, który działa w taki sam sposób jak XmlIgnore dla XmlSerializer.

Zapisz/Zmień

Operację wstawienia nowego dokumentu do bazy lub aktualizacji już istniejącego wykonuję w taki sam prosty sposób np.:
public void Save(ExpressionEntity ex)
{
 using (var session = Store.OpenSession())
 {
  session.Store(ex);
  session.SaveChanges();
 }
}
Zaczynam, więc od otworzenia sesji pracy z RavenDB. Następnie przy pomocy metody Store wrzucam do bazy nowy dokument lub aktualizuję już istniejący. Jak widać nie przejmuję się, którą operację właściwie wykonuję. Jeśli obiekt będzie miał pusty/zerowy Id to zostanie utworzony nowy dokument, a właściwość Id zostanie uaktualniona. Jeśli Id zostanie wypełnione Raven DB spróbuje odszukać istniejący dokument i go uaktualnić. Jeśli nie znajdzie dokumentu o podanym identyfikatorze utworzy go. Metoda SaveChanges to coś w rodzaju Commit'a: wysyła zmiany do serwera.

Usuń

Usuwanie dokumentów z bazy również jest bardzo proste np.:
public void Remove(ExpressionEntity ex)
{
 using (var session = Store.OpenSession())
 {
  session.Delete<ExpressionEntity>(session.Load<ExpressionEntity>(ex.Id));
  session.SaveChanges();
 }
}
Jak widać przed usunięciem ładuję obiekt z bazy na podstawie identyfikatora. Jest to potrzebne ponieważ inaczej dostaniemy błąd z informacją, że obiekt nie jest doczepiony do sesji (is not associated with the session, cannot delete unknown entity instance). Nie znalazłem innego sposobu rozwiązania problemu.

Ważna uwaga

Powyżej przedstawiłem dwie przykładowe metody Save oraz Remove, które odpowiednio tworzą/aktualizują jeden dokument oraz usuwają jeden dokument z bazy danych. W przypadku wykonywania operacji na wielu dokumentach takie podejście będzie zbyt kosztowne. Jeśli zachodzi potrzeba pracy z wieloma dokumentami to powinniśmy zrezygnować z takiego mikro zarządzania i jak najwięcej operacji wykonywać w ramach jednej sesji.

Podsumowanie

Dzisiaj pokazałem jak wykonywać podstawowe operacje na dokumentach. Jak widać jest to bardzo proste. Właściwie nie musimy nic robić aby nakłonić nasze obiekty do współpracy z Raven DB. Nie potrzebujemy również mappera relacyjno-obiektowego. To jest właśnie to co bardzo mi się spodobało w Raven DB.

Definiuję sobie klasę, tworzę jej instancje i po prostu wrzucam je do bazy danych. Nie piszę komend INSERT, UPDATE lub DELETE. Nie instaluję dodatkowych komponentów. Nie konfiguruję mapowań pomiędzy tabelami i obiektami. Nie definiuję schematu bazy danych. Jest prosto, szybko i przyjemnie czyli tak jak powinno być!

Na koniec znowu podsumujmy co już mamy/umiemy:
  • Bazę Raven DB osadzoną w aplikacji hostującej.
  • Kod inicjalizujący Raven DB.
  • Dostęp do Raven Studio i API REST'owego.
  • Obiekty POCO jakie mogą zostać umieszczone w Raven DB.
  • Kod dodający/zmieniający/usuwający dokumenty.

19/01/2012

RavenDB (cz. 1)

Home

Raven DB to dokumentowa baza danych, przedstawiciel trendu NoSQL, opracowana przez Ayende i jego zespół. Całkiem niedawno bo w październiku 2011 Ayende był zresztą w Polsce i opowiadał o swoim dziecku. Kilka miesięcy temu w jednym ze swoich pet project postanowiłem zmienić technologię dostępu do danych i wybór padł właśnie na Raven DB.

Kilka słów wstępu

Projekt ten to program LanguageTrainer wspomagający naukę słówek. Pomysł jego napisania narodził się ponad rok temu kiedy "ponownie" rozpocząłem naukę języka niemieckiego. W sieci nie znalazłem programu, który spełniałby moje oczekiwania. Zdecydowałem więc, że napiszę coś szytego na miarę. Początkowo rozpoczęło się niewinnie: listę słówek i ich tłumaczeń trzymałem w pliku. Dokładniej mówiąc był to po prostu wynik serializacji XML'owej. Podejście to przestało się sprawdzać kiedy do głowy zaczęły mi przychodzić kolejne pomysły: A może dodać bardziej zaawansowane wyszukiwanie, jakieś statystyki itd.

Początkowo pomyślałem o przejściu na bazę relacyjną, ale stwierdziłem, że tutaj nie nauczę się niczego nowego. Pomyślałem, że fajnie będzie wypróbować coś zupełnie nowego, z czym nie miałem jeszcze do czynienia, a ponieważ wcześniej czytałem trochę o Raven DB i wiedziałem, że ma .NET'owe API, wybrałem właśnie ją.

Z bazą tą już trochę pracuję, z braku czasu niezbyt intensywnie, ale zebrałem już trochę doświadczeń i postanowiłem sie nimi podzielić. Planuję serię postów, w których opiszę jak pracuje się z Raven DB na przykładzie swojego pet project. Nie będzie to jednak typowy tutorial omawiający wszystkie zagadnienia. Poruszę tylko te, z którymi miałem okazję się zapoznać.

Zaczynamy

LanguageTrainer to typowa aplikacja grubego klienta (WPF + MVVM). Nie chciałem jednak uzależniać się od połączenia z serwerem. Dlatego zdecydowałem się na hostowanie Raven DB w procesie aplikacji. Po ściągnięciu paczki z binariami na swoje potrzeby skopiowałem więc katalog EmbeddedClient. W tym momencie zaznaczę jeszcze, że warstwę dostępu do danych mam ukrytą za dobrze zdefiniowanym interfejsem, a więc aplikacja nie wie z jakim źródłem danych pracuje. Poniżej fragment kodu z klasy implementującej ten interfejs, odpowiedzialny za zainicjowanie Raven DB.

public EmbeddableDocumentStore Store { get; private set; }

...

public void Init(string dir)
{
 Store = new EmbeddableDocumentStore
 {
  DataDirectory = dir,
  UseEmbeddedHttpServer = true,
 };

 Store.Initialize();
}
 


Instancja EmbeddableDocumentStore posłuży nam później do otwierania sesji pracy z Raven DB, wykonywania zapytań itd. W kodzie tym ustawiam tylko dwie właściwości. DataDirectory wskazuje katalog roboczy, w którym Raven DB utworzy odpowiednią strukturę katalogów i gdzie będzie trzymał dane. Za pierwszym razem inicjalizacja zajmie więc trochę więcej czasu.

Istotna jest też właściwość UseEmbeddedHttpServer. Dzięki ustawieniu jej na true mam dostęp do napisanej w Silverlight aplikacji Raven Studio do zarządzania dokumentami oraz, co mniej ważne z mojej perspektywy, dostęp REST'owy do dokumentów. Oczywiście ponieważ Raven DB jest hostowany przez mój program, aplikacja Raven Studio będzie dostępna tylko wtedy, kiedy uruchomiony jest LanguageTrainer.

Numer portu z jakiego będzie korzystał serwer HTTP ustawiamy w pliku konfiguracyjnym aplikacji dodając następujący wpis:
<appSettings>
    <add key="Raven/Port" value="8888" />
</appSettings>

To jednak nie wszystko. Jeśli w tym momencie spróbujemy uruchomić Raven Studio otrzymamy od serwera HTTP taki błąd:

Could not find file Raven.Studio.xap, which contains the Raven DB Studio functionality. Please copy the Raven.Studio.xap file to the base directory of RavenDB and try again.

Plik Raven.Studio.xap znajdziemy w dystrybucji Raven DB i musimy umieścić go w katalogu z jakiego uruchamiamy aplikację. Ja zrobiłem to w ten sposób, że dodałem go do projektu, Build Action ustawiłem na Content, a Copy to Output Directory na Copy if newer. Testowałem w przeglądarkach Chrome, Firefox oraz IE i działa.

Podsumowanie

Tyle na dzisiaj. Podsumujmy co już mamy/umiemy:
  • Bazę Raven DB osadzoną w aplikacji hostującej.
  • Kod inicjalizujący Raven DB.
  • Dostęp do Raven Studio i API REST'owego.
W następnej części opiszę jak wykonywać podstawowe operacji na dokumentach.

24/12/2011

Życzenia świąteczne

Home



Z okazji Świąt Bożego Narodzenia życzę czytelnikom i czytelniczkom wszystkiego dobrego, żeby najbliższe dni spędzili w wymarzony sobie sposób, z bliskimi sobie ludźmi, a w nowym roku pomyślności i wielu ciekawych wpisów na tym blogu :)

Serdecznie pozdrawiam,
Michał Komorowski

01/12/2011

Wczytywanie podzespołów do domeny aplikacyjnej

Home

Platforma .NET, dzięki mechanizmowi refleksji, pozwala na dynamiczne wczytywanie do programu podzespołów (ang. assembly). Pozwala to w łatwy sposób pisać rozszerzane przy pomocy pluginów aplikacje i na wiele innych rzeczy. Ostatnio potrzebowałem wykorzystać ten mechanizm do własnych celów. Aby zwiększyć bezpieczeństwo, postanowiłem ładować podzespoły do odzielnych domen aplikacyjnych. W ten sposób, jeśli po załadowaniu podzespołu i wykonaniu jego kodu pojawi się błąd, główna domena aplikacyjna pozostaje nienaruszona.

Użycie osobnej domeny aplikacyjnej przydaje się również kiedy chcemy wczytywać i usuwać załadowane assembly z pamięci. Problem polega na tym, że po załadowaniu podzespołu do domeny nie ma możliwości aby go z niej usunąć. Można jednak osiągnąć podobny rezultat ładując podzespoły do oddzielnych "roboczych" domen, a potem skorzystać z metody AppDomain.Unload, która usuwa z pamięci domenę i wszystkie wczytane do niej podzespoły. (To pewne uproszczenie. Jeśli assembly zostało załadowane do kilku domen to zostanie usunięte dopiero jeśli usuniemy wszystkie domeny ją używające.)

Jak to zrobić? W sieci można znaleźć kilka podejść, ja użyłem w uproszczeniu następującego sposobu:
public static class SeperateDomainAssemblyLoader
{
  [Serializable]
  private class InternalLoader
  {
    public void LoadAndProcess(string assemblyPath)
    {
      Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);

      var assembly = Assembly.LoadFrom(assemblyPath);
      //...
    }
  }

  private static AppDomain _domain = AppDomain.CreateDomain("SeperateDomainAssemblyLoader");

  public static void LoadAndProcess(string assemblyPath)
  {
    InternalLoader internalLoader = (InternalLoader)(_domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InternalLoader).FullName));
    internalLoader.LoadAndProcess(assemblyPath);
   }
}
i kod testujący:
...
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
SeperateDomainAssemblyLoader.LoadAndProcess(somePath);
...
Niestety ku mojemu zdziwieniu program wypisał na ekran dwa razy tą samą nazwę domeny. Jak to możliwe, przecież jak wół stoi, że instancja klasy InternalLoader została stworzona w osobnej domenie. Uważni czytelnicy już pewnie widzą błąd. Ja też go znalazłem, ale chwilę zajęło mi uzmysłowienie sobie, co robię nie tak.

Zapomniałem o tym, że obiekty pomiędzy domenami aplikacyjnymi przekazywana są domyślnie przez wartość. Co z tego, że utworzyłem obiekt w osobnej domenie, skoro i tak pracowałem z jego kopią. Jeśli InternalLoader dziedziczyłby z MarshalByRefObject to pracowałabym nie z prawdziwym obiektem ale z proxy i wszystko byłoby dobrze. Poprawka jest więc bardzo prosta:
...
private class InternalLoader : MarshalByRefObject
{
  ...
}
...

Problem z półką

Home

Jakiś czas temu próbując wykonać operację merge w TFS napotkałem na bardzo irytujący problem pod tytułem:

TF203015 The Item '' has an incompatible pending change.

Nie robiłem nic bardzo skomplikowanego. Najpierw pobrałem do gałęzi A zmiany umieszczone na półce (ang. shelve). Następnie, przy pomocy polecenia merge, chciałem do nich dodać zmiany z changeset'a z gałęzi B i w tym momencie pojawił się powyższy komunikat. Sprawdziłem też odwrotną kolejność czyli najpierw merge z gałęzi B do A, a potem pobranie kodu z półki ale błąd również wystąpił. Innymi słowy, zamiast zgłosić konflikt i umożliwić jego rozwiązanie TFS wypiął się i rzucił błędem.

Nie chciałem wykonywać "ręcznego" łączenia plików ponieważ to błędogenne i niewygodne. Zacząłem szukać rozwiązania i znalazłem sposób na obejście problemu. Daleki od ideału, ale lepszy rydz niż nic. Postąpiłem w następujący sposób:
  • Zainstalowałem Team Foundation Server Power Tools
  • Najpierw wykonałem operację merge z gałęzi B do gałęzi A.
  • Uruchomiłem Visual Studio 2010 Command Prompt.
  • Przeszedłem do katalogu z gałęzią A.
  • Wpisałem komendę tfpt unshelve
  • Wybrałem swoja półkę.
  • Rozwiązałem konflikty.
Jak widać jeśli korzystamy z komendy tfpt to zamiast otrzymać błąd dostaniemy listę wykrytych konfliktów i możliwość ich rozwiązania. Można? Ano można.

16/11/2011

Hawkeye

Home

Hawkeye .NET Runtime Object Editor to program, który znalazłem w sieci dobre dwa lata temu. W tym czasie wielokrotnie mi się przysłużył, a jest przydatny w szczególności tym, którzy pracują z technologią Windows Forms. W skrócie, pozwala modyfikować UI działającej aplikacji. Jego użycie jest proste. Wskazujemy myszką interesujący nas fragment aplikacji, a Hawkeye oznacza wybraną kontrolkę przy pomocy czerwonej ramki i wyświetla listę właściwości i prywatnych pól klasy, których wartości możemy modyfikować. Jak by tego było mało, Hawkeye pozwala również dynamicznie wywoływać metody dla wybranych obiektów.

Zastanawiamy się jak nasza aplikacja będzie wyglądać z różowym tłem? Chcemy przesunąć na próbę kontrolkę o 2 piksele w prawo? A może chcemy nacisnąć przycisk, który jest nieaktywny? Te i inne rzeczy osiągamy zmieniając wartości odpowiednich właściwości, w tym przypadku odpowiednio: BackColor, Location, Enabled. W trzecim wypadku możemy też wywołać dynamicznie metodę PerformClick. Pozwala to w łatwy i przyjemny sposób zobaczyć, jak przy danych ustawieniach, będzie w runtime'ie będzie wyglądała nasza aplikacja, bez potrzeby jej rekompilacji.

Hawkeye pozwala również poruszać się w hierarchii kontrolek tworzących interfejs i sprawdzić kto jest rodzicem interesującej nas kontrolki. Podaje też pełną nazwę klasy dla aktualnie wybranego obiektu. Przydaje się to, kiedy pracujemy z dużą, skomplikowaną aplikacją i chcemy dowiedzieć się gdzie u licha znajduje się kod obsługujący aktualnie widoczną kontrolkę. Przykład z życia. Ostatnio kumpel potrzebował znaleźć kod odpowiedzialny za pewną część UI. W tym celu chciał wyświetlić designera dla interesującego go okna i na tej podstawie dojść do kontrolki. Niestety Visual Studio odmówiło posłuszeństwa i przy próbie wyświetlenia designera raportowało błąd. Hawkeye rozwiązał problem w kilka minut.

Uwaga! Na dzień dzisiejszy, na stronie programu znajdują się jego dwie wersje do pobrania. Jedna działa tylko z aplikacjami skompilowanymi na platformę .NET 4, a druga z aplikacjami skompilowanymi na starsze wersje platformy.

28/10/2011

Wiele usług w jednym procesie 2

Home

W poście tym wrócę jeszcze do tematu uruchamiania kilku usług w jednym procesie. Otóż, ciekawe jest to, że można konfigurować to zachowanie już po zainstalowaniu usługi. Służy do tego, i nie tylko tego, program wiersza poleceń o nazwie sc. Poniżej przedstawiam przykład jego użycia.

Zacznijmy od pobrania konfiguracji usługi ABC przy pomocy komendy sc query ABC. Przykładowy wynik pokazałem poniżej.
SERVICE_NAME: ABC
TYPE               : 20  WIN32_SHARE_PROCESS
STATE              : 1  STOPPED
  (NOT_STOPPABLE,NOT_PAUSABLE,IGNORES_SHUTDOWN)
WIN32_EXIT_CODE    : 1077       (0x435)
SERVICE_EXIT_CODE  : 0  (0x0)
CHECKPOINT         : 0x0
WAIT_HINT          : 0x0
Jak widać usługa ABC może współdzielić proces z jakąś inną usługą. Efekt działania bardzo ładnie widać w menadżerze zadań. Jeśli uruchomimy usługi: ABC oraz ABCDebug to menadżer zadań pokaże tylko jeden proces.



Aby to zmienić należy użyć komendy sc config ABC type= own. Teraz, po uruchomieniu obu usług, menadżer zadań pokaże dwa procesy.



Do stanu pierwotnego wracamy przy pomocy komendy sc config ABC type= share. Warto zwrócić uwagę na jeszcze jedną rzecz. Spójrzmy na ten scenariusz.
  • Mamy dwie usługi współdzielące proces: ABC oraz ABCDebug..
  • Uruchamiamy obie i menadżer zadań pokazuje jeden nowy proces: WindowsService1.exe.
  • Zatrzymujemy usługę ABC.
  • Usługa ABCDebug nadal działa.
  • sc config ABC type= own
  • Uruchamiamy usługę ABC i teraz menadżer zadań pokazuje dwa procesy WindowsService1.exe.
  • Zatrzymujemy usługę ABC
  • sc config ABC type= share
  • Ponownie uruchamiamy usługę ABC i menadżer zadań znowu pokazuje jeden proces WindowsService1.exe.
Czyli w czasie kiedy zmienialiśmy konfigurację usługi ABC usługa ABCDebug cały czas działała i w niczym to nie przeszkadzało.

26/10/2011

Wiele usług w jednym procesie

Home

Istnieje kilka podejść do debugowania usług systemowych. Jeśli chcemy debugować już uruchomioną usługę to możemy skorzystać z opcji Attach to process.... Sprawa jest trudniejsza jeśli chcemy podłączyć się do usługi w momencie jej uruchamiania. W takim wypadku można w kodzie usługi wywołać metodę Debugger.Brake. Są też inne sposoby, na przykład sztuczne opóźnienie startu usługi, tak aby zdążyć się do niej podpiąć.

Ostatnio poznałem nowe, bardzo ciekawe podejście. Polega ono na stworzeniu dodatkowej, pomocniczej "pustej" usługi i zainstalowaniu jej w odpowiedni sposób razem z właściwą usługą. Strukturę przykładowego projektu widać na poniższym rysunku.



Projekt musi również zawierać dwa instalatory (klasa ServiceInstaller), po jednym dla każdej z usług.



W kodzie przekłada się to na coś takiego.
...
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
  this.serviceProcessInstaller1,
  this.serviceInstaller1,
  this.serviceInstaller2});
...
Instalację przeprowadzamy standardowo przy pomocy narzędzia InstallUtil. Zadba ono o to aby zainstalować obie usługi za jednym razem. Następnie, przy debugowaniu najpierw uruchamiamy tą drugą, dodatkową usługę, podczepiamy się do niej przy pomocy opcji Attach to process..., stawiamy pułapkę w kodzie pierwszej usługi i dopiero ją uruchamiamy. To zadziała ponieważ obie usługi zostaną uruchomione w jednym procesie, a co więcej otrzymujemy to za darmo. Odpowiada za to metoda ServiceIntaller.Install, której fragment przytaczam poniżej.
int serviceType = 0x10;
...
if (numberOfServices > 1)
{
  serviceType = 0x20;
}
...
NativeMethods.CreateService(databaseHandle, this.ServiceName, this.DisplayName, 0xf01ff, serviceType, (int) this.StartType, 1, str3, null, IntPtr.Zero, dependencies, servicesStartName, password);
...
CreateService to natywna metoda WinAPI, która dodaje usługę do bazy danych menadżera usług. Istotny jest jej piąty parametr serviceType, może przyjąć kilka wartości ale nas interesują dwie:
  • SERVICE_WIN32_OWN_PROCESS = 0x00000010 oznacza, że każda usługa działa w swoim procesie
  • SERVICE_WIN32_SHARE_PROCESS = 0x00000020 oznacza, że usługi mogą dzielić jeden proces
Jeśli liczba usług jest większa niż 1 to ServiceIntaller.Install używa flagi SERVICE_WIN32_SHARE_PROCESS, a w przeciwnym wypadku SERVICE_WIN32_OWN_PROCESS.

24/10/2011

PrintScreen

Home

Ostatnimi czasy zdarzyło się, że musiałem intensywnie korzystać z przycisku Print Scrn, tworzyć wiele zrzutów ekranu, a potem jeszcze edytować je w programie graficznym. W pewnym momencie stwierdziłem, że proces ten trzeba jakoś zautomatyzować. Z pomocą przyszedł program PrintScreen.

W wersji podstawowej jest bezpłatny. Reaguje na standardowy przycisk Print Scrn ale można to zmienić. Zrzuty ekranu wypluwa w wybranym formacie, do wskazanego katalogu. Nazwy plików mogą być generowane automatycznie np.: ScreenSchot001, ScreenSchot002 itd. Bardzo spodobało mi się to, że jeśli usuniemy np.: plik ScreenSchoot002 i w ten sposób powstanie luka w numeracji to zostanie ona zapełniona przez następny plik. Program umie również wyświetlić podgląd zrzutu, a przed zapisaniem na dysk umożliwia np.: zmianę wielkości obrazka. W zależności od konfiguracji robi zrzut całego ekranu, wybranego okna lub wskazanego fragmentu.

Nie jest to nic co przyprawia o zawrót głowy ale doskonale spełnia swoją funkcję - po prostu kawał dobrze wykonanej roboty. Program ten zagości na stale w mojej skrzynce narzędziowej i polecam go każdemu komu nie wystarcza standardowa funkcjonalność systemu operacyjnego dotycząca tworzenia zrzutów ekranu.

13/09/2011

Okazja do wzięcia udziału w ciekawych kursach

Home

Uniwersytet Stanforda organizuje zdalne kursy poświęcone uczeniu maszyn (ang. machine learning), sztucznej inteligencji oraz bazom danych. Zajęcia są bezpłatne i zaczynają się w październiku, a potrwają do grudnia. Kursy składają się nie tylko z wykładów, ale również z rożnych zadań do rozwiązania, będzie można również zadawać pytania. Moim zdaniem to wspaniała okazja, aby zdobyć albo odświeżyć dużo cennej wiedzy. Na każdy z tych wykładów zapisało się już ponad 50 tysięcy osób!

12/09/2011

IntelliTrace - problem ze zdarzeniem

Home

Jakiś czas temu pracując z IntelliTrace próbowałem zdefiniować zdarzenie diagnostyczne dla pewnej metody. Dla ustalenia uwagi niech jej sygnatura wygląda tak, jak poniżej.

string Flip(string s)

Moim celem było, aby opis zdarzenia zawierał wynik zwrócony przez metodę oraz wartość argumentu s. Inaczej mówiąc, aby w oknie IntelliTrace Events View w Visual Studio 2010 zdarzenie zarejestowane dla wywołania metody z argumentem s="Hello" i wynikiem "olleH" wyglądalo tak:

"olleH" Flip("Hello")

O definiowaniu zdarzeń IntelliTrace już pisałem (Własne zdarzenia IntelliTrace!, Własne zdarzenia IntelliTrace 2) dlatego nie będę opisywał całego procesu. Przytoczę już gotową definicję zdarzenia:
<DiagnosticEventSpecification enabled="true">
  <Bindings>
    <Binding>
      <ModuleSpecificationId>FibTest.exe</ModuleSpecificationId>
        <TypeName>Utilities</TypeName>
        <MethodName>Flip</MethodName>
        <MethodId>Utilities.Flip(System.String):System.String</MethodId>
        <ShortDescription _locID="shortDescription.Utilities.Flip">"{0}" Flip("{1}")</ShortDescription>
        <LongDescription _locID="longDescription.Utilities.Flip">"{0}" Flip("{1}")</LongDescription>
        <DataQueries>
          <DataQuery index="-1" maxSize="100" type="String" query="" />
          <DataQuery index="1" maxSize="100" type="String" query="" />
        </DataQueries>
        <ProgrammableDataQuery>
          <ModuleName></ModuleName>
          <TypeName></TypeName>
        </ProgrammableDataQuery>
    </Binding>
  </Bindings>
  ...
</DiagnosticEventSpecification>
Niestety ku moje zdziwieniu to nie zadziałało. Zdarzenie zostało zarejestrowane, ale w oknie IntelliTrace Events View zamiast zobaczyć upragniony wynik otrzymałem komunikat: An error occured while fetching the data for this event. Zajrzałem, więc do wcześniej zdefiniowanych przez siebie zdarzeń i przypomniałem sobie o jednej rzeczy. Aby odwołać się do wartości zwróconej przez metodę należy ustawić atrybut onReturn.
...
<Binding onReturn="true">
...
Niestety to też nie pomogło. Ponownie zajrzałem więc do wcześniej przygotowanych zdarzeń i na pierwszy rzut oka wszystko wyglądało tak samo. Po chwili zastanowienia doszedłem do wniosku, że przyczyną kłopotów może być to, że próbuję odwołać się zarówno do wartości argumentów jak i do wartości zwracanej przez metodę. Wcześniej czegoś takiego nie próbowałem. Zamiast jednego przygotowałem więc dwa zdarzenia. W jednym odczytuję wartość argumentu przekazanego do metody, a w drugim wynik zwrócony przez metodę.
...
<Binding onReturn="false">
  ...
  <ShortDescription _locID="shortDescription.Utilities.Flip">Flip("{0}")</ShortDescription>
  <LongDescription _locID="longDescription.Utilities.Flip">Flip("{0}")</LongDescription>
  <DataQueries>
    <DataQuery index="1" maxSize="100" type="String" query="" />
  </DataQueries>
  ...
</Binding>
...
...
<Binding onReturn="true">
  ...
  <ShortDescription _locID="shortDescription.Utilities.Flip">Flip returns "{0}"</ShortDescription>
  <LongDescription _locID="longDescription.Utilities.Flip">Flip returns "{0}"</LongDescription>
  <DataQueries>
    <DataQuery index="-1" maxSize="100" type="String" query="" />
  </DataQueries>
  ...
</Binding>
...
To zadziałało, zostały zarejestrowane dwa zdarzenia, jedno z opisem Flip("Hello"), a drugie z opisem Flip returns "olleh". Moim zdaniem to spore ograniczenie IntelliTrace, ale nie ma rady i trzeba o tym po prostu pamiętać.

11/09/2011

Londyn - trochę nietypowa wycieczka

Home

Londyn to miasto odwiedzane rokrocznie przez miliony turystów. Twierdza Tower, Tower Bridge, Pałac Buckingham to tylko z niektórych miejsc, które znajdują się na liście do zobaczenia dla wielu odwiedzających. Ja też nie omieszkałem pokazać się w tych i wielu innych miejscach, ale w tym poście chciałbym zachęcić do wizyty w innym, trochę bardziej nietypowym miejscu. Mam na myśli Thorpe Park czyli wesołe miasteczko zlokalizowane pod Londynem (ok. 40 minut pociągiem + 10 minut autobusem). Bilet kosztuje dla dorosłych 40 funtów, ale cenę można zbić o 35% przy zakupie online lub nawet 50% przy zakupie grupowym (7 biletów).

Miasteczko oferuje bardzo dużo dla miłośników przeciążeń, chyba nawet więcej niż konkurencyjny park Alton Towers. Wspomnę tylko o Stealth, w którym osiągamy prędkość ok. 130 km/h w ciągu 1.8 sekundy, maksymalne przeciążenie to prawie 5G (wciska w fotel), a najwyższy punkt kolejki znajduje się 60m nad ziemią. Robi niesamowite wrażenie, do tego stopnia, że bez zastanowienia zdecydowałem się na drugą przejażdżkę.

Z rzeczy praktycznych to w parku jest tłoczno nawet poza sezonem. Mówię tutaj o weekendach, w ciągu tygodnia może być lepiej. W związku z tym należy przygotować się na długie czekanie, nawet do 2 godzin aby dostać się na kilkunasto sekundową przejażdżkę! Kolejki można ominąć na dwa sposoby. Pierwszy to tzw. single riders. Polega to na tym, że najpierw do wagoników ładowane są osoby z normalnej kolejki i jeśli zostają wolne miejsca (bo ktoś nie chce jechać sam tylko z przyjaciółmi) to wsiada osoba z kolejki dla single riders. Kolejka ta przeważnie jest dużo krótsza niż normalna, ale po pierwsze nie jest dostępna wszędzie, a po drugie dużo zależy od szczęścia. Równie dobrze może być tak, że czas oczekiwania będzie taki jak w normalnej kolejce.

Drugi sposób jest dużo skuteczniejszy, ale nie ma nic za darmo i to dosłownie. Można kupić bilety fast tracks, które pozwalają wejść na atrakcje bez czekania w kolejce. Pojedynczy bilet (jeden przejazd na jednej atrakcji) to koszt od 2 do 5 funtów. Czy warto? Niech każdy odpowie sobie samemu, czy woli czekać czy nie.

Wizytę w Thorper Park szczerze polecam wszystkim, którzy lubią takie atrakcje, szczególnie, że jest zlokalizowany blisko Londynu. Ja bawiłem się tam naprawdę dobrze. Linki do poprzednich postów z serii na temat życia w Londynie:

04/09/2011

IntelliTrace - schemat XSD

Home

Swego czasu w postach Własne zdarzenia IntelliTrace! oraz Własne zdarzenia IntelliTrace 2 opisałem jak zmodyfikować plik CollectionPlan.xml zawierający plan działania IntelliTrace (historycznego debuggera) tak, aby zdefiniować swoje własne zdarzenie IntelliTrace (ważny punkt w historii działania programu kiedy IntelliTrace nagrywa stan aplikacji). Ostatnio wróciłem do tego zagadnienia i "bawię się" testując różne możliwości IntelliTrace. Niestety czasami, po zmodyfikowaniu pliku CollectionPlan.xml, przy próbie uruchomienia debuggera otrzymywałem błąd np.:

error VSLG4001: The specified collection plan is invalid: 'C:\CollectionPlan.xml'. More information: The 'type' attribute is invalid - The value 'Object' is invalid according to its datatype 'urn:schemas-microsoft -com:visualstudio:tracelog:ClrType' - The Enumeration constraint failed.

Komunikat jest czytelny, wartość Object dla atrybutu type jest niedozwolona. Metodą prób i błędów można wywnioskować, jakie wartości są poprawne, ale to męczące i niewydajne. Pomyślałem więc, że skoro zawartość pliku CollectionPlan.xml to dokument XML, to musi on być walidowany przy pomocy odpowiedniego schematu XSD. Ale gdzie go szukać? Przejrzałem zawartość katalogu instalacyjnego Visual Studio 2010 i niczego nie znalazłem.

Stwierdziłem więc, że zajrzę do biblioteki Microsoft.VisualStudio.IntelliTrace (pisałem o niej w poście Poznaj swój program), która umożliwia programową analizę logów IntelliTrace, ale nie tylko. Biblioteka ta wykorzystywana jest również przez program IntelliTrace.exe (o tym też pisałem w poście Używanie IntelliTrace poza Visual Studio 2010!), który znajdziemy w katalogu instalacyjnym Visual Studio 2010. Program ten służy do uruchomienia historycznego debuggera i kiedy używamy IntelliTrace z poziomu Visual Studio, to korzystamy właśnie z tego programu. Skoro tak to doszedłem do wniosku, że walidacja konfiguracji i schemat XSD znajdują się właśnie w tej bibliotece.

Okazało się to strzałem w dziesiątkę. Bibliotekę załadowałem do .NET Reflector'a. Najpierw zlokalizowałem w zasobach komunikat z błędem. Następnie sprawdziłem gdzie jest używany i znalazłem tylko jedno takie miejsce, a stamtąd już szybko doszedłem do wywołania metody public static XmlSchema GenerateXmlSchema(). Niestety okazało się, że nie mogę jej wywołać z własnego kodu, ponieważ znajduje się w klasie internal class ConfigMessagePacker. Skopiowałem więc jej kod (ok. 1200 linii) do swojego programu i po chwili miałem już XSD.

31/08/2011

Krótko o instalowaniu ServicedComponent

Home

W dwóch poprzednich artykułach na temat zarządzanych komponentów COM+ pisałem, że instaluje się je przy użyciu narzędzia regsvcs.exe. Tak oczywiście jest, ale ostatnio ku swojemu zaskoczeniu zauważyłem, że jest to opcjonalne. Jeśli nie zainstalujemy takiego komponentu z poziomu konsoli (np.: regsvcs.exe MyComponent.dll) to zostanie on zainstalowany automatycznie przy pierwszym wywołaniu jego konstruktora.
[assembly: ApplicationName("MyComponent")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: System.Reflection.AssemblyKeyFile("MyComponent.snk")]
[assembly: ApplicationAccessControl(false)]
public class MyComponent: ServicedComponent
{
  ...
}

...

//Jeśli komponent nie został wcześniej zainstalowany to zostanie zainstalowany w tym momencie
using (MyComponent cmp = new MyComponent())
{
  ...
}


25/08/2011

Jeszcze o ServicedComponent

Home

W tym poście wrócę do tematu aplikacji modelu COM+ napisanych w kodzie zarządzanym, który to poruszyłem w poprzednim artykule. Tym razem chciałbym zwrócić uwagę na problem wersjonowanie takich komponentów. Upraszczając, chodzi o różnicę pomiędzy katalogiem, z jakiego komponent został zainstalowany w systemie, a katalogiem, w którym znajduje się biblioteka z komponentem jakiej używa dana aplikacja. W szczególności mogą to być inne katalogi np.: c:\Install oraz c:\bin.

W takim przypadku łatwo może dość do sytuacji, w której binaria w obu lokalizacjach będą się różnić. Objawy będą różne w zależności od trybu aktywacji (pisałem o nich poprzednio).W przypadku Aplikacji biblioteki tak długo jak w obu katalogach będą znajdować się biblioteki skompilowane dla tego samego środowiska (32 lub 64 bitowego) nie będzie żadnego problemu (poza bałaganem). Co ciekawe znaczenie ma tylko liczba bitów, wersje bibliotek mogą być inne, różna może być nawet liczba udostępnianych przez komponent metod. Natomiast w przypadku kiedy w jednym katalogu będzie wersja 32 bitowa, a w drugim 64 bitowa przy próbie skorzystania z komponentu dostaniemy wyjątek ComException o treści Klasa niezarejestrowana....

W trybie Aplikacji serwerowej liczba bitów nie ma znaczenia. Znaczenie ma jednak zawartość bibliotek w obu lokalizacjach. Jeśli będzie różna debugowanie nie będzie możliwe. Jeśli zmieni się interfejs, na przykład do biblioteki zostanie dodana nowa metoda, ale nowa wersja nie zostanie zainstalowana, to przy próbie jej użycia pojawi się wyjątek.

Generalnie problem dotyczy też natywnych komponentów COM+ (piekło COM+) ale z takimi komponentami nie pracowałem dlatego nie znam szczegółów.

23/08/2011

Debugowanie ServicedComponent

Home

ServicedComponent to klasa umożliwiająca tworzenie zarządzanych komponentów/klas, które mogą być użyte w aplikacjach COM+ oraz mogą korzystać z usług COM+. Jedną z takich usług jest na przykład pula obiektów, czyli coś podobnego do puli połączeń z tą różnicą, że możemy w niej umieścić instancje naszej własnej klasy.

Aby stworzyć taką specjalną klasę należy wydziedziczyć ją ze wspomnianej klasy ServicedComponent. Do tej pory nie miałem okazji z niej korzystać, dlatego napotkałem pewne kłopy przy debugowaniu takich zarządzanych komponentów COM+ (dalej będę używał po prostu pojęcia komponent).

Należy zacząć od tego, że są dwa tryby aktywacji komponentów COM+ (zarządzanych lub nie). W pierwszym (tzw. Aplikacja biblioteki/Library application) komponent aktywowany jest w procesie aplikacji, która z niego korzysta. W drugim trybie natomiast (tzw. Aplikacja serwera/Server application) aktywacja przeprowadzana jest przez dedykowany proces. Tworząc taki komponent możemy określić tryb aktywacji przy pomocy atrybutu ApplicationActivationAttribute. Tryb ten można również zmienić już po zainstalowaniu komponentu (przy pomocy narzędzia regsvcs.exe) w konsoli zarządzania w przystawce Usługi składowe (ang. Component services). Znajdziemy ją w lokalizacji C:\Windows\System32\comexp.msc.

Debugowanie takiego zarządzanego komponentu różni się w zależności od trybu aktywacji. Zacznijmy od pierwszego przypadku czyli tzw. Aplikacji biblioteki. Tutaj sprawa generalnie jest prosta. Skoro komponent aktywowany jest w procesie aplikacji, która z niego korzysta to wystarczy postawić pułapkę w odpowiednim miejscu np.: w kodzie komponentu i tyle. Jest jedno ale. To nie zadziała jeśli aplikacja korzystająca z komponentu została skompilowana na platformę .NET 4.0. Komponent będzie zwracał poprawne wyniki ale jak do tej pory nie udało mi się zmusić VS 2010 do zatrzymania się na pułapce ustawionej w kodzie komponentu albo przejść do tego kodu przy pomocy F11. Jeśli zmienimy platformę na przykład na .NET 3.5 to problem z debugowaniem nie wystąpi.

Trzeba również wiedzieć, że w przypadku tego trybu aktywacji, jeśli mamy zainstalowany komponent 32 bitowy, to proces, który chce z niego skorzystać również musi być 32 bitowy. Analogicznie dla 64 bitów. Informację o błędzie dostaniemy już przy próbie wywołania konstruktora komponentu.

W przypadku trybu "serwerowego" komponent aktywowany jest w innym procesie, więc wersja platformy czy nawet liczba bitów nie mają znaczenia (również przy debugowaniu). Z drugiej strony mamy inny problem ponieważ musimy doczepić się do tego procesu aby go zdebugować czyli skorzystać z funkcji Debug->Attach to Process.... Interesujący nas proces nazywa się dllhost.exe. Kłopot w tym, że na liście może znajdować się kilka procesów o tej nazwie. Pierwsze przybliżenie uzyskamy zawężając listę do tych procesów, które w kolumnie Type mają wartość Managed.... W przypadku gdy jest ich kilka można skorzystać z programu Process Explorer i sprawdzić identyfikator procesu, który korzysta z biblioteki z naszym komponentem.

W jednym z kolejnych postów wrócę jeszcze do tematu i napisze o problemach z wersjonowaniem omawianych komponentów.

22/08/2011

Londyn - metro (wskazówki)

Home

W ostatnim poście dotyczącym metra w Londynie obiecałem, że dam kilka wskazówek o tym jak z niego korzystać (o innych środkach transportu nie napiszę bo nie używam). Nie będzie to jakaś wiedza tajemna, ale kiedy jest się po raz pierwszy w Londynie, to nie wszystko jest oczywiste.
  • Nie opłaca się kupować biletów za gotówkę. Tak zakupiony bilet dla pierwszej strefy, w porównaniu do biletu elektronicznego, może być nawet 2 razy droższy. Dla dalszych stref różnica jest mniejsza, ale na przykład większość atrakcji turystycznych znajduje się w pierwszej strefie.
  • Osobiście korzystam z karty Oyster czyli odpowiednika Warszawskiej Karty Miejskiej z tą jednak różnicą, że Oyster oferuje więcej możliwości. Na taką kartę można załadować bilet okresowy, ale również określoną sumę pieniędzy do wydania na przejazdy tzw. pay as you go. Z metra korzystam raz na jakiś czas więc jest to dla mnie idealne rozwiązanie.
  • Kartę Oyster kupujemy (ładujemy) w automatach biletowych ustawionych w metrze lub w kasach. Przy zakupie zostanie pobrany zwrotny depozyt wysokosci 5 funtów.
  • Nie na każdej stacji znajdziemy kasy biletowe, automaty natomiast tak.
  • Ważne!!!Korzystając z karty Oyster trzeba koniecznie pamiętać żeby zbliżyć kartę do czytnika (oznaczone sa charakterystycznymi żółtymi kółkami) przy wejściu ale i przy wyjściu z metra. W innym wypadku zostaniemy obciążeni dodatkową opłatą (nie wiem ile dokładnie). W większości wypadków nie ma z tym problemu ponieważ nie przejdziemy przez barierki bez ważnego biletu. Niestety na niektórych stacjach nie ma barierek. Czasami zdarzy się również, że barierki są otwarte ale wtedy też trzeba pamiętać o czytniku.
  • Po zarejestrowaniu karty Oyster na tej stronie będziemy mogli śledzić historię naszych podróży oraz doładować kartę on-line. Jeśli chcemy kupić bilet okresowy na czas dłuższy niż tydzień rejestracja jest wymagana.
  • Ważne!!! Jeśli mamy kartę pay as you go to mamy zagwarantowane, że danego dnia nie wydamy na przejazdy więcej niż koszt biletu dziennego. Szczegóły można znaleźć tutaj.
  • Warto korzystać ze strony Transport for London. Znajdziemy tam bardzo dobrą wyszukiwarkę połączeń oraz informacje o planowanych remontach poszczególnych linii. Generalnie remonty przeprowadzane są w weekendy.
  • Nie wszystkie stacje metra mają windę.
  • Na stacjach metrach można znaleźć broszurki z planem metra - bardzo przydatne.
  • Jeśli ktoś źle znosi wysokie temperaturę lub duchotę, to schodząc do metra (szczególnie korzystając z linii głębinowych) warto zaopatrzyć się w butelkę wody.
  • Szczegółowy cennik znajdziemy tutaj
Linki do poprzednich postów z serii na temat życia w Londynie:

18/08/2011

Londyn - zamieszki

Home

Z publikacją tego posta czekałem aż zamieszki w Londynie ucichną i sprawa zostanie zapomniana przez media. No i się doczekałem. Obecnie w polskich mediach ciężko znaleźć jakąś wzmiankę na ten temat podczas gdy jeszcze jakiś czas temu można było odnieść wrażenie, że Londyn to strefa wojny.

O zamieszkach usłyszałam po raz pierwszy, kiedy spytała mnie o nie żona. Potem także rodzina i znajomi pytali mnie czy nic mi nie jest, czy jestem bezpieczny itd. Nie dziwię się temu, bo kiedy przeglądam artykuły na temat zamieszek, okraszone strasznymi zdjęciami, to przyznam, że nie wyglądało to fajnie. Osobiście oprócz większej liczby policjantów na ulicach nie widziałem innych skutków rozruchów.

To pokazuje jaka może być różnica pomiędzy rzeczywistością, a obrazem świata przedstawianym przez prasę. Nie twierdzę, że problemu nie było bo problem oczywiście był (jest) i to duży. Wielu ludzi zostało poszkodowanych i przeżyło ciężkie chwile. Ale tak nie było w całym mieście. W tym samym czasie, wbrew temu co pokazywały media, w Londynie toczyło się normalne życie.

Linki do poprzednich postów z serii na temat życia w Londynie:

07/08/2011

Kłopoty z Client Profile

Home

The type or namespace name 'SomeType' does not exist in the namespace 'SomeNamespace' (are you missing an assembly reference?)

Sądzę, że każdy programista .NET spotkał się z powyższym błędem kompilacji. Nie jest to nic wyjątkowego i rozwiązanie problemu jest bardzo łatwe, wystarczy dodać do projektu referencję do brakującej biblioteki. Czy oby na pewno?

Kilka dni temu napotkałem powyższym błąd kompilatora i rozwiązanie problemu zajęło mi sporo więcej czasu niż normalne kilkanaście sekund. Na początku zdziwiłem się ponieważ niekompilujący się projekt aplikacji zawierał potrzebną referencję, kilka minut wcześniej sam ją dodałem. Na wszelki wypadek podpiąłem ją jednak jeszcze raz i przekompilowałem wszystkie potrzebne projekty ale nic to nie dało. Restart Visual Studio również nie pomógł. Kilka kolejnych prób kompilacji również spełzło na niczym.

Trochę zrezygnowany zabrałem się do przeglądania ustawień felernego projektu i zwróciłem uwagę na to, że korzysta on .NET Framework 4 Client Profile. Wcześniej nie miałem z tym problemu ale przypomniałem sobie, że w projektach tego typu nie można korzystać z bibliotek, których nie ma w NET Framework 4 Client Profile. Na próbę zmieniłem opcję Target framework na .NET Framework 4 i okazało się to strzałem w dziesiątkę.

W ramach testu zacząłem zmieniać różnym projektom opcję Target framework na .NET Framework 4 Client Profile i sprawdzać czy się kompilują. Okazało się, że prawie wszystkie skompilowały się bez żadnego problemu. Dalsze eksperymenty doprowadziły mnie do następującego wniosku:

Błąd zostanie zgłoszony jeśli w projekcie korzystającym z NET Framework 4 Client Profile użyjemy biblioteki, która bezpośrednio lub pośrednio korzysta z czegoś co nie znajduje się w NET Framework 4 Client Profile.

Przez użyjemy rozumiem np.: stworzenie instancji klasy. Samo dodanie referencji do biblioteki czy nawet zaimportowanie przestrzeni nazw przy pomocy using nie spowoduje błędu. Ważne jest również to, że biblioteka powodująca błąd może znajdować się gdzieś daleko w łańcuchu referencji, co jeszcze utrudnia sprawę. Należy również wiedzieć, że o przynależności lub nie do NET Framework 4 Client Profile nie decydują opcje kompilacji ale to z czego korzystamy w danej bibliotece. Listę "zabronionych" rzeczy można znaleźć tutaj. W moim przypadku błąd powodowała "zabroniona" biblioteka System.Data.OracleClient, z której korzysta Microsoft.Practices.EnterpriseLibrary.Data.dll, którą z kolei używam ja.

Sądzę, że warto o tym pamiętać aby zaoszczędzić sobie nerwów szczególnie, że kiedy dodajemy nowy projekt aplikacji (WPF, WinForms, konsola) to domyślnie będzie on korzystał .NET Framework 4 Client Profile. W większości przypadków jest to dla nas przezroczyste ale zawsze może trafić się ten jeden raz.

01/08/2011

Londyn - metro (trochę narzekania)

Home

Dzisiaj napiszę o metrze londyńskim, które należy do najstarszych i najbardziej rozbudowanych na świecie, a z którego w porównaniu do innych środków transportu korzystam najczęściej. Dwanaście linii wijących się pod miastem w porównaniu do jednej budowanej 25 lat w Warszawie robi wrażenie i budzi zazdrość. Ja chciałbym jednak pokazać, że jeśli choć na chwilę zapomnimy o liczbie linii to metro warszawskie zacznie wygrywać z londyńskim pod wieloma względami.

Zacznę od tego, że w londyńskim metrze jest bardzo duszno i gorąco. Nawet jeśli na dworze jest zimno i założyliśmy kurtkę to po zejściu do metra z pewnością ją zdejmiemy. Na przewarzającej liczbie stacji i w pociągach niestety brakuje klimatyzacji. Czytałem, że w najbliższym czasie na jednej z linii mają zostać wprowadzone pociągi z klimatyzacją ale to kropla w morzu potrzeb. Póki co przemieszczanie się metrem to przyjemnych nie należy, a zarząd metra zaleca aby w podróż wybrać się z butelką wody.

Komfortu nie poprawia fakt, że stacje w londyńskim metrze są bardzo małe, szczególnie te głębinowe. Jest akurat tyle miejsca aby zmieścił się pociąg i niewiele więcej. Biorąc pod uwagę to co widziałem, tak na oko przeciętna szerokość peronu to 3-4 metry. Pod tym względem Warszawa bije Londyn na głowę. Mniejsze są również wagony (z ciekawostek to na poszczególnych liniach mają różne rozmiary). W Warszawie bez problemu stoję wyprostowany, w Londynie mieszczę się po środku wagonu.

W londyńskim metrze brakuje również tzw. komór rozprężających powietrze. Powoduje to, że pociąg wjeżdżający na stację wpycha przed sobą ogromną masę powietrza, która uderza w pasażerów. Może to i drobiazg ale pokazuje, że metro w Warszawie należy do nowoczesnych. Potwierdza to, również fakt, że wszystkie stacje metra w Warszawie przystosowane są dla niepełnosprawnych (mam na myśli windy), a w Londynie nie.

Z innych rzeczy to jadąc metrem w Londynie mam wrażenie, że tory do najprostszych nie należą. Obserwując wagon jadący przed nami albo za nami można zauważyć jak nim rzuca, czasami jest jakby trochę wyżej, a czasami niżej :). Metro w Londynie jest również po prostu drugie, nawet biorąc pod uwagę wyższe zarobki. Koszt jednego przejazdy w ramach pierwsze strefy to na tą chwilę 1.9 funta szterlinga i to przy założeniu, że korzystamy z karty magnetycznej (tzw. Oyster). Tradycyjne bilety są jeszcze droższe.

Tyle narzekania. Ideałem byłoby metro nowoczesne jak w Warszawie ale z taką liczbą linii jak w Londynie (może moje wnuki tego doczekają). W kolejnych postach chciałbym dać kilka wskazówek jak korzystać z metra w Londynie.

Linki do poprzednich postów z serii na temat życia w Londynie:

29/07/2011

Londyn - kosze na śmieci

Home

Z ciekawostek. W Londynie jest bardzo mało koszy. W gruncie rzeczy jeśli mamy do wyrzucenia jakiś papierek, niedopałek papierosa itp. do mamy dwa wyjścia: schować do kieszeni lub wyrzucić na ulicę. Wiele osób, a w szczególności palacze wybierają drugą opcję. Służby sprzątające są jednak bardzo sprawne dlatego miasto nie jest zaśmiecone.

Ciekawe jest natomiast dlaczego tych koszy jest tak mało. Kolega stwierdził, że to pozostałość po czasach kiedy aktywna była Irlandzka Armia Republikańska. Kosz na śmieci to doskonałe miejsce na podłożenie bomby i dlatego usunięto je z ulic tylko, że potem "zapomniano" je przywrócić.

Linki do poprzednich postów z serii na temat życia w Londynie:

27/07/2011

IntelliTrace - Reaktywacja

Home

Już kawał czasu temu w artykule Używanie IntelliTrace poza Visual Studio 2010! opisałem w jaki sposób uruchomić narzędzie IntelliTrace poza środowiskiem Visual Studio 2010. Przedstawiona przeze mnie metoda miała jednak wadę. Log z nagranym przebiegiem wykonania programu zawierał "tylko" informację o wywołaniach metod czyli drzewo wywołań, a brakowało w nim informacji o zdarzeniach diagnostycznych czyli ważnych punkty w historii wykonania programu np.: wykonanie zapytania do bazy danych.

Ostatnio udało mi się znaleźć ostatni element układanki. Otóż aby log IntelliTrace zawierał wszystkie niezbędne informacje wystarczy wykonać, oprócz opisanych już przeze mnie rzeczy, jeszcze jeden krok czyli zmodyfikować plik CollectionPlan.xml z planem działania/konfiguracją narzędzia. Znajdujemy w nim linię:
...
<DiagnosticEventInstrumentation enabled="false">
...
i zamieniamy na:
...
<DiagnosticEventInstrumentation enabled="true">
...

22/07/2011

Londyn - przechodzenia przez ulicę

Home

W Polsce od maleńkiego uczono mnie, że przez ulicę należy przechodzić na pasach i kiedy pali się zielone światło. Swego czego przekonałem się również ile wynosi mandat za przechodzenie na czerwonym świetle:) Kiedy wyjeżdżam za granicę z założenia stosuję te same zasady, choćby dlatego, żeby nie płacić potencjalnie wyższych mandatów niż w Polsce.

W Londynie (nie wiem czy jest tak w całym Zjednoczonym Królestwie), co mnie bardzo zaskoczyło, sprawa ma się zupełnie inaczej. Zdecydowana większość osób przechodzi na czerwonym świetle i jest to właściwie zasadą. Początkowo miałem jednak pewne skrupuły. O tym, że jest to "dozwolone" przekonałem sie kiedy zobaczyłem, że policja nie reaguje na ten niecny proceder nawet jeśli przechodzi się na czerwonym świetle tuż koło właśnie ruszającego radiowozu!

Z czego wynika to przyzwolenie na łamanie prawa? Od kolegi dowiedziałem się, że wbrew pozorom wszystko dzieje się w majestacie prawa. Z ciekawości poszukałem informacji na ten temat i wygląda na to, że kolega miał rację. Otóż przepisy dotyczące przechodzenia przez jezdnię sformułowano przy użyciu wyrażeń "should/should not" albo "do/do not" (rada/zalecenie), a nie "must/must not" (zakaz/nakraz). Innymi słowy napisano, że "nie powinno" się przechodzić na czerwonym świetle zamiast, że "jest zabronione/nie wolno" przechodzić na czerwonym świetle.

Takie podejście do przechodzenia przez ulicę z pewnością przyspiesza poruszanie się po Londynie. Trzeba jednak pamiętać, że robimy to na swoją odpowiedzialność. Jeśli zostaniemy potrąceni przez samochód to będzie to nasza wina.

Linki do poprzednich postów z serii na temat życia w Londynie:

19/07/2011

See[Mike]Code

Home

Dzisiaj rozmawiając z kolegami zeszliśmy na temat rekrutacji programistów. Między innymi rozmawialiśmy o narzędziach wspomagających ten proces takich jak Codility. Rozmowa ta przypomniała mi, że swego czasu natknąłem się ba bardzo proste ale pomysłowe narzędzie pozwalające na żywo, zdalnie sprawdzić jak potencjalny kandydat radzi sobie z programowaniem. Miałem trudności z przypomnieniem sobie adresu strony dlatego ku pamięci publikuję ten post. Przy okazji sądzę, że narzędzie to może przydać się innym. Mianowicie chodzi o See[Mike]Code.

Zasada działania jest bardzo prosta. Wchodzimy na stronę See[Mike]Code, klikamy przycisk New Interview Site i otrzymujemy dwa adresy. Jeden wysyłamy do kandydata, a drugi zachowujemy dla siebie. O wyznaczonej porze prosimy aby kandydat uruchomił przeglądarkę i wszedł na podany adres. Jego oczom ukarze się taki widok:



Kiedy wejdziemy pod drugi adres wyświetlona zostanie dość podobna strona. Teraz prosimy kandydata aby wykonał jakieś proste zadanie programistyczne np.: słynne FizzBuzz, a jego poczynania możemy obserwować na ekranie naszego komputera. See[Mike]Code nie udostępnia takich dobrodziejstw jak IntelliSense ale w przypadku prostych zadań, takich jak wspomniane FizzBuzz, nie jest to konieczne.

Jestem przekonany, że na polskim rynku nie mamy sytuacji, w której jak to napisał Jeff Atwood na swoim blogu:

Like me, the author is having trouble with the fact that 199 out of 200 applicants for every programming job can't write code at all. I repeat: they can't write any code whatsoever.

Sądzę jednak, że dobrze mieć w swoim repertuarze takie narzędzie.

13/07/2011

Londyn - pogotowie

Home

Dzisiaj idąc do pracy byłem świadkiem jak pogotowie udzielało pomocy kobiecie, która zasłabła na moście London Bridge. Co w tym takiego ciekawego, ze postanowiłem o tym napisać? Moją uwagę zwrócił środek transportu jakim przyjechał ratownik czyli rower. Słyszałem o tym ale po raz pierwszy widziałem.

Temat podrążyłem trochę bardziej i dowiedziałem się, że w Londynie pogotowie posługuje się oczywiście samochodami ale również motocyklami (motorcycle responders) i rowerami (cycle responders). Dwa ostatnie środki transportu używane są w najbardziej ruchliwych/zakorkowanych dzielnicach tak aby nawet w godzinach szczytu dotrzeć do poszkodowanego w jak najkrótszym czasie. Za takim rowerowym pogotowiem wysyłany jest również ambulans. Jeśli jednak okaże się, ze poszkodowany nie musi zostać przetransportowany do szpitala to ratownik może odwołać wyjazd. Dzięki takiemu podejściu, od momentu wprowadzenia pogotowania rowerowego (10 lat temu) ambulanse musiały wyjechać o 20 tysięcy razy mniej, co przekłada się przecież na konkretne pieniądze. Inny niespotykany w Polsce "wynalazek" to Ambulance community responder czyli jednoosobowe pogotowie samochodowe, w którym pracują ochotnicy. Ich celem jest dotarcie do pacjenta i utrzymanie go przy życiu, aż do przybycia ambulansu, których liczba jest ograniczona.

O Brytyjskiej służbie zdrowia słyszałem dwie opinie, ze jest zła albo, że jest bardzo zła. Mam nadzieję, ze nie będę miał okazji tego zweryfikować ale przynajmniej pogotowie wygląda tutaj na dobrze zorganizowane i sprawnie działające.

Linki do poprzednich postów z serii na temat życia w Londynie:

10/07/2011

Londyn - ceny jedzenia

Home

Dzisiaj wrócę do tego jak żyje się w Londynie, a konkretniej do cen artykułów spożywczych i innych. Trochę zakupów już zrobiłem i na podstawie paragonów sporządziłem listę wybranych produktów wraz z cenami w supermarketach (Tesco, Lidl, Sainsbury's). Lista ta znajduje się na końcu postu. Oczywiście nie jest kompletna ale pi razy oko pozwala zorientować się w poziomie cen. Po miesiącu pobytu sporządzę natomiast podsumowanie ile przez ten czas kosztowało mnie jedzenie.

Czy jest drogo? Trochę na pewno ale ogólnie możemy być dumni bo ceny w Polsce z pewnością osiągnęły Europejski albo prawie Europejski poziom. Szkoda, że to samo nie dotyczy pensji :) Dodam jeszcze, że dość powszechne są promocje rodzaju kup 3 paczki wędliny, a zapłać za dwie co przy większych zakupach pozwala znacząco obniżyć ich koszt.

Sporo droższe jest na pewno żywienie się na mieście np.: za kebab zapłaciłem 5£, za narodowe danie Brytyjczyków czyli Fish&Chips 10£, a za świeżo zrobioną kanapkę z szynką (takie 2 trójkąty) 3.5£. Duży kubełek ryżu z kurczakiem curry lub zestaw obiadowy Sushi w sieciówce Wasabi (bardzo polecam) to koszt odpowiednio ok. 5£ i ok. 6,5£. Są to ceny z centrum Londynu, więc z pewnością trochę zawyżone ale tak czy inaczej jest drożej niż w Polsce. Jeśli chodzi o bardzo ważną rzecz czyli cenę piwa to 1 pinta (~568 ml) w pubie kosztuje ok. 3,5£. Do tematu piwa i pubów wrócę jeszcze później.
  • Coca cola 2L 1,5£ - 1,89£
  • Woda mineralna 2L 0,45£
  • Piwo 0,81£-1,1£
  • Sok pomarańczowy 1 L 1,19£ - 1,56£
  • 1 kg jabłek 1,5£
  • 1 kg pomidorów 1,99£
  • 1 kg cebuli1,2 £
  • 1 banan 0,18£
  • Mała puszka pomidorów 0,41£
  • Słoik ogórków konserwowych 0,99£
  • Mały słoik oliwek 0,59£
  • Paczka Spaghetti 0,79£
  • 0,5 kg soli 0,15£
  • 50g pieprzu 0,55£
  • 0,5 kg wołowego mięsa mielonego 2,69£
  • 4 steki wieprzowe (nie pamiętam wagi, tak na 2 obiady) 3,29£
  • Chleb pełnoziarnisty 1,25 £
  • Zupka w proszku 0,19£
  • Czteropak jogurtów Activia 1,66£
  • masło 1,6£
  • 1 Kg paczka frytek McCane 1,86£
  • Paczka wędliny 140g 1,15£
  • 15 jajek 1,25£
  • 1 kg proszku do prania Persil
  • batonik Snicker 0,50£ - 0,60£


Linki do poprzednich postów z serii na temat życia w Londynie:

07/07/2011

Bug w Visual Studio

Home

Niedawno spotkałem się z zabawnym bugiem w Visual Studio. Zauważyłem go w VS 2005, ale udało mi się go odtworzyć w VS 2010, zresztą nie jest to trudne. Poniżej bardzo prosty kawałek kodu, który pozwoli wyjaśnić o co chodzi:
public class Fun
{
  private string s = null;

  public void Test(string s)
  {
    Test2();
  }


  public void Test2()
  {
    Console.WriteLine(s);
  }
}
...
new Fun().Test("Ala ma kota");
Stawiamy pułapkę na początku metody Test oraz Test2 i uruchamiamy program. Po zatrzymaniu programu na pierwszej pułapce sprawdzamy wartość parametru s oraz prywatnej składowej klasy o tej samej nazwie. Otrzymamy taki wynik:



Powtarzając tą samą operację po zatrzymaniu programu na drugiej pułapce otrzyma natomiast taki wynik:



W "magiczny" sposób składowa prywatna, która powinna mieć wartość null przyjęła nagle wartość "Ala ma kota". Analogicznie parametr s przyjął wartość null zamiast "Ala ma kota". Na tym nie koniec. Poniższy obrazek pokazuje jak środowisko VS raportuje NullReferenceException i równocześnie pokazuje, że podejrzana referencja nie jest pusta.



Oczywiście nie ma w tym żadnej magii. Moim zdaniem to ewidentny bug w Visual Studio, spowodowany tym, że środowisko nie uwzględnia zasięgu obowiązywania składowych klas czy też parametrów metod. Z drugiej strony nazywanie w taki sam sposób zmiennych lokalnych, parametrów czy składowych klas, których zasięgi leksykalne pokrywają się, nie jest dobrym pomysłem bo prowadzi do bałaganu i pomyłek. W każdym razie wbrew pozorom w szale debugowania łatwo się na to złapać.

06/07/2011

Londyn - początek

Home

Życie podąża różnymi ścieżkami, a mnie ostatnio przywiało do Londynu. Zawsze lubiłem czytać o życiu i pracy w innych krajach dlatego postanowiłem, że korzystając z okazji będę dzielił się swoimi wrażeniami na blogu.

Zacznę od tego, że jestem w podróży służbowej, a więc jestem w tej komfortowej sytuacji, że przyjechałem na gotowe. Z tego powodu nie mogę jednak opisać z autopsji jak wygląda szukanie pracy czy mieszkania w Londynie. Skupię się, więc na innych rzeczach: ile kosztuje mnie życie z wyłączeniem kosztów około mieszkaniowych, jak wyglądają dojazdy do pracy, czy miasto mi się podoba itd. Tym samym mocno odbiegnę od zwyczajnej tematyki bloga ale mam nadzieję, że się spodoba. Posty na ten temat chciałbym publikować co kilka dni ale będzie to raczej zlepek luźno powiązanych z sobą wpisów niż z góry zaplanowana seria.

Na początek kilka zdań o tym jak idzie mi dogadywanie się z autochtonami. Wyjeżdżając do Londynu miałem z jednej strony wiele powodów sądzić, że nie będę miał z tym większych problemów ale miałem również trochę obaw. Jeśli ktoś chciałby porównać swój poziom znajomości angielskiego z moim to uczę się go od czasów podstawówki przy czym intensywną naukę zacząłem tak naprawdę w liceum. Mam też zdany egzamin CAE. Do tej pory angielskiego używałem głównie do czytania dokumentacji technicznej, książek, forów czy blogów. Rzadziej pisałem po angielsku, a już bardzo rzadko miałem okazję rozmawiać po angielsku, w szczególności na tematy inne niż techniczne.

Przechodząc do setna to po tych kilku dniach pobytu muszę powiedzieć, że jest dobrze. W przeważającej liczbie przypadków nie mam żadnych albo prawie żadnych problemów z porozumiewaniem się po angielsku. Równocześnie zdarzyło się jednak, że w czasie rozmowy, pomimo poproszenia o powtórzenie, z powodu akcentu nie byłem w stanie wyłowić z wypowiedzi bardzo wielu słów i mogłem zrozumieć tylko ogólny sens wypowiedzi. Mam jednak nadzieję, że to tylko kwestia czasu i osłuchania. Muszę również przyznać, że jestem mile zaskoczony ponieważ większość do tej pory spotkanych przeze mnie osób nie mówi z bardzo silnym akcentem, który utrudniałby zrozumienie.

Jeśli chodzi o mówienie to otrzymałem nawet komplement od rodowitego Anglika, że mój angielski jest bardzo dobry :) Co prawda moim zdaniem rozmawiając z rzeczonym Anglikiem popełniłem sporo błędów gramatycznych i czasem brakowało mi słów ale to pokazuje, że najważniejsze to żeby się nie bać i mówić, mówić i jeszcze raz mówić. Dla mieszkańców danego kraju, w tym przypadku dla Brytyjczyków, najważniejsze jest przecież to, że mówi się w ich języku, a więc nie powinniśmy być dla siebie zbyt surowi. To przypomina mi anegdotę jaką opowiedział mi kolega z Polski. Otóż kiedy ubiegał się o pracę to odbył rozmowę z headhunter'em, który stwierdził, że z tym jego angielskim to mogło by być dużo lepiej, że popełnia błędy itd. Pomimo sceptycyzmu headhunter'a odbył rozmowę z Anglikami, a oni nie mieli zastrzeżeń i go zatrudnili. Dodam jeszcze, że jego angielski jest moim zdaniem lepszy od mojego.

Na koniec osobom, które wyjeżdżają za granicę i podobnie jak ja rzadko rozmawiają po angielsku polecam przed samym wyjazdem wykupić kilka godzin konwersacji z lektorem aby się rozgadać. Ja tak zrobiłem i sądzę, że pomogło.