Zastosowanie LambdaEqualityComparer

Jakiś czas temu Maciej Aniserowicz na swoim blogu opublikował kod klasy LambdaEqualityComarer

Bardzo często chcielibyśmy aby równość elementów jakiejś klasy zależała tylko od jakiegoś pola (najczęściej od ID), aby to osiągnąć należy nadpisać metody GetHashcode i Equals tej klasy jest to jednak praca dosyć żmudna i powtarzalna, dodatkowo okazuje się że kod ten należy często pisać ponieważ wiele klas wywołuje te metody w celu porównania elementów, przykładem takich klas są kontrolki WPF zawierające Listy jak np. ComboBox. Aby pracę tą trochę skrócić można użyć następującego fragmentu kodu:

    class User
    {
        /// <summary>
        /// Identyfikator
        /// </summary>
        public int ID { get; set; }

        // Inne pola

        private LambdaEqualityComparer<User, int> _comparer = new LambdaEqualityComparer<User, int>(u => u.ID);

        public override int GetHashCode()
        {
            return _comparer.GetHashCode(this);
        }

        public override bool Equals(object obj)
        {
            return _comparer.Equals(this, obj as User);
        }
    }

Aby ten kod działał w 100% poprawnie należy jeszcze zmodyfikować kod Maćka tak aby uwzględniała fakt iż któryś z parametrów metody Equals może być nullem.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            #endregion Private members 

            #region Constructors 
            public LambdaEqualityComparer(Func<T, TValue> converter) 
            { 
                this.converter = converter; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         return converter(x).Equals(converter(y)); 
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                { 
                        return converter(obj).GetHashCode(); 
                 }
            }
            #endregion 
        } 

Na koniec dodam że implementację tą można rozbudować zgodnie z wzorcem dekoratora, co daje nam możliwość wykorzystania istniejących implementacji IEqualityComparer'a.

class LambdaEqualityComparer<T, TValue> : IEqualityComparer<T> 
        { 
            #region Private members 
            private Func<T, TValue> converter; 
            private IEqualityComparer<TValue> realComparer;
            #endregion Private members 

            #region Constructors 
           public LambdaEqualityComparer(Func<T, TValue> converter) : this(converter, null) { }

            public LambdaEqualityComparer(Func<T, TValue> converter, IEqualityComarer<TValue> realComparer) 
            { 
                this.converter = converter; 
                this.realComparer = realComparer; 
            } 
            #endregion Constructors 

            #region IEqualityComparer<T> Members 
            public bool Equals(T x, T y) 
            { 
                if (x == null && y == null)
                {
                      return true;
                 } 
                  else if (x == null || y == null)
                 {
                         return false;
                 }
                  else
                  {
                         if  (realComparer == null)
                         {
                                  return converter(x).Equals(converter(y)); 
                          }
                          else
                          {
                                   return realComparer.Equals(converter(x), converter(y));
                           }
                  }
            }
            public int GetHashCode(T obj) 
            {
                if (obj == null)
               {
                       return 0;
                }
                else
                {
                       if (realComparer == null)
                       { 
                              return converter(obj).GetHashCode(); 
                        }
                         else
                         {
                                  return realComparer.GetHashCode(converter(obj));
                          }
                 }
            }
            #endregion 
        } 

Niestandardowe generowanie kluczy głównych w NHibernate

Robiąc ostatnio mały projekt postanowiłem w końcu wykorzystać NHibernate, na temat tego ORM'a jest w necie masa informacji więc nie będę się na jego temat rozpisywał. Podobnie nie będę opisywał konfiguratora Fluent NHibernate, który jest znacznie bardziej intuicyjny niż tradycyjne pliki hbm (oparte na XML).

Problemem przed jakim stanąłem był fakt że moja aplikacja miała współpracować z bazą na której działała inna aplikacja, posiadająca dosyć charakterystyczną politykę generowania kluczy głównych. Która nijak nie pasowała do żadnej standardowej polityki zawartej standardowo w Bibliotece hibernate'a.

Na szczęście NHibernate jest przygotowany na taką ewentualność, jego API zawiera interfejs IIDentifierGenerator, który umożliwia pisanie własnych polityk generowania kluczy. Interfejs ten zawiera jedną metodę Generate.

public class MyGenerator:IIDentiferGenerator
{
    public object Generate(ISessionImplementor session, Object obj)
   {
        return Guid.NewGuid()
    }
}

Pierwszy parametr zapewnia dostęp do obiektów związanych z konfiguracją połączenia z bazą danych oraz stanem tego połączenia, drugim parametrem jest obiekt dla którego generowany jest klucz. Przykład powyżej dla każdego obiektu zwróci wartość statycznej metody NewGuid która nadaje się na klucz główny ponieważ z definicji jest to Global Unique Identifier. Jednak generowanie nowego Guida nie jest niczym nadzwyczajnym po za tym biblioteka NHibernate'a już ją zawiera.

Częstym sposobem generowania kluczy głównych jest procedura składowana (lub fragment zapytania SQL) która zwraca najniższą nieużywaną wartość którą aplikacja może wykorzystać jako klucz dla danej tabeli.

Aby uruchomić takie zapytanie pod kontrolą NHibernate należy wykorzystać właściwości Connection i Batcher parametru session przekazanego do metody generate.

public class MyGenerator:IIDentiferGenerator
{
    public object Generate(ISessionImplementor session, Object obj)
   {
        using(IDbCommand command = session.Connection.GenerateCommand())
        {
            command.CommandText = "exec GetNextId()";
            using(IDataReader  reader = session.Batcher.ExecuteReader())
            {
                if (reader.Read())
                {
                     return reader.GetInt(0);
                }
                else
                {
                      throw new Exception ("Nie da się wygenerować klucza.");
                }
            }
        }
    }
}

Klasa przedstawiona powyżej jedną poważną wadę: jest bardzo sztywna tj. trudno byłoby ją wykorzystać do generowania klucza dla dowolnej encji. Informację na temat tabeli w której należy sprawdzać "Zajętość" danego identyfikatora można uzyskać tylko przez weryfikację typu parametru obj metody Generate. Wykorzystanie tego faktu sprawiłoby to że programista musiałby dla każdego obsługiwanego typu dopisywać kilka linijek kodu w tej metodzie co generowałoby masę powtarzalnego kodu.

Znacznie lepszym rozwiązaniem jest Implementacja Interfejsu IConfigurable zawierającego metodę Configure która przyjmuje jako parametry następujące wartości:

type – typ dla jakiego jest konfigurowana klasa

params – Słownik klucz <=> wartość przechowujący dodatkowe dane konfiguracyjne.

d – informacja na temat silnika bazodanowego przechowującego dane.

Parametrem nas interesującym jest params zawiera on dane na temat tabeli w której zapisywany jest dany typ oraz kolumny będącej kluczem głównym. Nie jestem pewien czy dla każdego silnika bazodanowego nazwy kluczy są takie same dlatego nie podaję tutaj konkretnego fragmentu kodu.

Pozostaje jeszcze tylko kwestia jak powiedzieć NHibernate'owi żeby dla danej klasy używał naszego generatora. We FluentHibernate wymaga to wpisania jednej linijki w klasie Mapującej:

Id(obj => obj.ID).GeneratedBy().Custom<MyGenerator>()

Edit: 02-10-2010

Podczas prac w NHibernate zauważyłem że zachowuje się on dosyć specyficznie podczas zapisu więcej niż jednego obiektu jedego typu w ciągu jednej tranzakcji. Otóż najpierw generuje on klucze główne dla wszystkich nowych obiektów, a następnie dokonuje ich zapisu. Jeżeli teraz oprzemy nasz mechanizm zapisu na zapytaniu do bazy to musimy się liczyć z faktem iż za każdym razem generowanie klucza będzie się odbywać na niezmienionej bazie. Więc zapytanie wywołane za każdym razem zwróci tą samą wartość. Należy to uwzględnić w kodzie, najczęściej przez duplikację części logiki zapytania, co jest oczywistą wadą tego rozwiązania, zaletą jego zaś jest mniejsza ilość zapytań co zazwyczaj poprawia aplikację pod względem wydajnosciowym.

PHP Cast patern

Podczas prac w PHP(z którego na szczęście rzadko korzystam) bardzo wkurza mnie fakt iż nie jestem w stanie zdefiniować (przy pomocy PHP Doca) iż jakaś funkcja zwraca tablicę elementów określonego typu. Przez co chcąc prze-iterować po wyniku takiej funkcji mam lekko utrudniony dostęp do pól i metod danej klasy (uzupełnianie kodu Eclipse nie radzi sobie z taką sytuacją zupełnie). Dlatego jakiś czas temu wpadłem na pomysł dodawania do każdej (a właściwie prawie każdej) nowej klasy statycznej metody Cast:

class Moja {

	/**
	 * 
	 * Pseudorzutowanie na typ
	 * @param mixed $item
	 * @return Moja
	 */
	public static function Cast($item) {
		return $item;
	}

	public function JakasFunkcja() {
		;
	}	
}

Teraz mogę sobie w kodzie spokojnie zwracać wyniku typu tablicowego i iterować po nim:

pictures/Code.jpg

W tej chwili jedyną wadą tego rozwiązania jaka mi przychodzi do głowy jest dodatkowa ilość generowanego kodu.

Nowa wersja bloga

Korzystając z okazji przymusowego nieróbstwa związanego ze zwolnieniem lekarskim postanowiłem dokończyć pracę nad Blogiem.

A że nie chciało mi się bawić z instalacją Flex Builder'a na Ubuntu przepisałem całość na "gołego" PHP'a.

W czasie prac nad nowym wyglądem postanowiłem zastąpić swoją bardzo niedoskonałą Captchę zabawką ze stajni Googla noszącym nazwę "ReCaptchia". Jest to rozwiązanie darmowe, bardzo skuteczne(większość udanych masowych łamań tego systemu opiera się na pracy Hindusów) , posiada wsparcie dla osób niewidomych, a na dodatek (jak mi się zdawało) jest łatwe w instalacji na każdej stronie.

O ile z wyświetleniem dialogu nie było większych problemów:

echo recaptcha_get_html($publicKey);

O tyle weryfikacja poprawności wprowadzonego obrazka okazała się znacznie trudniejsza ponieważ standardowe wywołanie:

$resp = recaptcha_check_answer ($privateKey,
                                $_SERVER["REMOTE_ADDR"],
                                $_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"]);
if($resp->is_valid)
{
    //TODO: Obsługa zapisu
} else {
    // TODO: Wyświetlić komunikat o błędzie
}

Sprawiała że moja strona kończyła się komunikatem 'Could not open socket'.

Po dłuższym śledztwie okazało się że komunikat ten pojawia się wielu użytkownikom a błąd z nim związany jest nierozwiązany od dwóch lat link.

Jest on bardzo często spowodowany blokowaniem przez firmy hostujące połączeń wychodzących na port 80. Ponieważ walka z OVH o otwarcie portu wydała mi się nie do wygrania (Błąd fsockopen Connection refused jest dość częsty na stronach hostowanych przez tą firmę – wystarczy wpisać ten komunikat w google) postanowiłem poszukać obejścia, którym okazały się być serwery Proxy, które przekierowują połączenie z jednego portu na inny port innego serwera.

Recaptchia nie wspiera Proxy na szczęście biblioteka PHP która zapewnia dostęp do Recaptchia jest w pełni Open Source'owa więc otworzyłem ją sobie i zmieniłem metodę _recaptcha_http_post dodając jej parametry $proxyHost = null i $proxyPort = null do których można przekazać adres i port serwera proxy. Oczywiście musiałem zmodyfikować też ciało funkcji dodając kod modyfikujący treść zapytania HTTP, oraz musiałem zmodyfikować funkcję recaptcha_check_answer tak aby mogła przekazać informacje na temat proxy do ww. metody.

Ostatecznie kod sprawdzający poprawność tekstu z obrazka wygląda następująco:

$resp = recaptcha_check_answer ($privateKey,
                                $_SERVER["REMOTE_ADDR"],
                                $_POST["recaptcha_challenge_field"],
                                $_POST["recaptcha_response_field"],
                                array(),
                                '190.90.128.233', #adres serwera proxy
                                8080);

Moje zmiany w bibliotece są wysłane do googla jako załącznik do Zadania nr 80 w projekcie Recaptcha mam nadzieję że zostaną włączone do oficjalnej wersji tej biblioteki.

Pierwszy wpis na blogu

W koncu się zdobylem i Blog utworzylem zobaczymy czy bedzie mi się chcialo go utrzymywac. Na razie nie zachwyca ani funkcjonalnoscia ani wygladem ale bede nad tym pracowal

Strona jest narazie tylko i wyłącznie wersja beta.

Prosze o sugestie w komentarzach.