22 lutego 2012, 23:35 dario Komentarze (0)

Rozszerzanie IRepository

W poprzedniej notce pokazałem jak przy pomocy bardzo prostego interfejsu IRepository oraz QueryObject można zrealizować warstwę dostępu do danych. W zalezności od wielkości projektu może się okazać, że ciut brakuje funkcjonalności. Co zatem robić?

Z założenia nie chcemy już więcej zmieniać naszego interfejsu. Jak więc przykładowo wykonać operację pobrania rekordu z bazy i ustanowienia na nim blokady? W bazowej implementacji nie mamy takiej metody... i nie będziemy jej dodawać do interfejsu. Na szczęście można go łatwo rozszerzyć definiując metody rozszerzające (Extension Methods).

Powyższy problem można rozwiązać nastepująco:

 

public static class RepositoryExtensions
{
    public static T GetWithLock<T>(this IRepository<T> r, object id)
    {
        return r.Execute(new GetWithLockQuery<T>(id));
    }

    private class GetWithLockQuery<T> : IQuery<T, T> where T : class
    {
        private readonly object _id;

        public GetWithLockQuery(object id)
        {
            _id = id;
        }

        public T Build(ISession session)
        {
            return session.Get<T>(_id, LockMode.Upgrade);
        }
    }
}

 

Przykład użycia:

 

IRepository<User> repo...
var user = repo.GetWithLock(5);

Powyżej utworzyliśmy metodę rozszerzającą funkcjonalność naszego repozytorium o możliwość pobrania rekordu z jednoczesnym założeniem blokady. W ten sam sposób możemy dodawać kolejne potrzebne rozszerzenia w zależności od potrzeb w projekcie. Jak widać daje to nieograniczone możliwości. A nasz interface jest nadal prosty i... elastyczny.

 

Inny przykład? A jak bym chciał sobie w dowolnym miejscu w kodzie pisać zapytania korzystając z NHibernate.Linq? Nic łatwiejszego. :)

Dodajemy metodę rozszerzającą, która zwróci nam IQueryable<T> i po kłopocie. Oto przykład:

 

public static IQueryable<T> Queryable<T>(this IRepository<T> r) where T : class
{
    return r.Execute(new QueryableQuery<T>());
}

private class QueryableQuery<T> : IQuery<T, IQueryable<T>> where T : class
{
    public IQueryable<T> Build(ISession session)
    {
        return session.Query<T>();
    }
}

 

To w sumie by było na tyle w tym temacie. :)

Tagi:

NHibernate

13 lutego 2012, 21:44 dario Komentarze (0)

QueryObject Pattern i bardzo proste IRepository<T>

W poprzednim wpisie chciałem pokazać jak w dłuższej perspektywie opasłe obiekty repozytoriów są mało użyteczne i nie spełniają swoich teoretycznych założeń. Mimo to jednak postanowiłem zostawić w swojej infrastrukturze interfejs IRepository<T>, który w przedstawionej poniżej formie jest tak naprawdę fasadą dla ISession. NHibernate.ISession jaki znamy to bardzo duża klasa z wieloma metodami, które bardzo rzadko używamy, a niektóre wręcz wcale. Po co mają mi się "plątać" w kodzie?

public interface IRepository<TEntity>
{
    /// <summary>
    /// Get entity from data store.
    /// </summary>
    /// <param name="id">Identifier</param>
    /// <returns>Entity retrieved from data store.</returns>
    TEntity Get(object id);

    /// <summary>
    /// Add entity to data store.
    /// </summary>
    /// <param name="entity">Entity to add.</param>
    /// <returns>The same entity with filled identifier property.</returns>
    TEntity Add(TEntity entity);

    /// <summary>
    /// Delete entity from data store.
    /// </summary>
    /// <param name="entity">Entity to be deleted.</param>
    void Delete(TEntity entity);

    /// <summary>
    /// Find single entity in data store using given expression.
    /// </summary>
    /// <param name="expression">Expression used to find single entity.</param>
    /// <returns>Single entity.</returns>
    TEntity Single(Expression<Func<TEntity, bool>> expression);

    /// <summary>
    /// Find list of entities in data store using given expression.
    /// </summary>
    /// <param name="expression">Expression used to find entities.</param>
    /// <returns>List of entities.</returns>
    IEnumerable<TEntity> List(Expression<Func<TEntity, bool>> expression);

    /// <summary>
    /// Execute query and return defined type of result.
    /// </summary>
    /// <typeparam name="TResult">Result defined in query.</typeparam>
    /// <param name="query">Query to execute.</param>
    /// <returns>Result defined in query.</returns>
    TResult Execute<TResult>(IQuery<TEntity, TResult> query);
}

Jak widać interface jest bardzo prosty i łatwo rozszerzalny. Zdefiniujmy sobie zatem jeszcze jeden brakujący interface IQuery<,>.

public interface IQuery<TEntity, TResult>
{
    TResult Build(ISession session);
}

Uchhhh... Skomplikowany... ;) Żarcik, ale do rzeczy.

IRepository<T> posiada kilka prostych metod dających możliwość pobrania obiektu z bazy, usunięcia, czy dodania nowego. Oprócz tego można utworzyć na szybko nieskomplikowane zapytanie posługując się metodami Single czy List. Przykład prostego zapytania, które wyszukuje użytkownika z podanym adresem email:

IRepository<User> repo....
User u = repo.Single(x => x.Email == "developer@dario-g.com");

Podobnie można skontruować zapytanie zwracające listę używając metody List. Jak już jesteśmy przy tworzeniu zapytań to wróćmy zatem do sposobu ich zapisu i wykonywania.

Metoda Execute służy właśnie do tego, aby wykonywać zapytania zdefiniowane w osobnych obiektach QueryObject. Zdefiniujmy sobie zatem jakieś zapytanie.

namespace Domain.Security.Queries
{
    public class FindActiveUserByEmailQuery : IQuery<User, User>
    {
        public string Email { get; set; }

        public User Build(ISession session)
        {
            return session.QueryOver<User>()
                .Fetch(x => x.Roles)
                .Where(x => x.Email == Email)
                .And(x => x.IsActive == true)
                .SingleOrDefault();
        }
    }
}

Zapytanie nie jest trudne do zrozumienia, ale bardzo ładnie pokazuje w jaki sposób się tworzy obiekt Query. Widać jak na dłoni, że rezultatem takiego zapytania może być dowolna struktura. Może to być pojedynczy obiekt jak w przykładzie powyżej, lista IList<User>, czy także zwykła tablica obiektów User[]. Co więcej. Nic nie stoi na przeszkodzie, aby zapytanie było skonstruowane przez:

  • session.Query<> (NHibernate.Linq)
  • session.CreateCriteria
  • session.CreateQuery
  • session.CreateSQLQuery

Specjalnie zostawiłem klauzulę namespace. Chciałem pokazać, że wszystkie obiekty zapytań możemy sobie ładnie pogrupować, aby łatwiej było je odnaleźć podczas pisania kodu. To jeszcze dla uzupełnienia przykład jak wywołać takie zapytanie:

var query = new FindActiveUserByEmailQuery() { Email == "developer@dario-g.com" };
var user = repo.Execute(query);

Można spokojnie pogodzić ze sobą te dwa wzorce i znacznie ułatwić sobie pracę. Ja już zauważyłem, że mam większą swobodę, kolejne funkcjonalności robi mi się szybciej, a struktura projektu jest bardziej przejrzysta.

W kolejnym wpisie o tym co jeszcze można dodatkowo z IQuery<,> zrobić. :)

Tagi:

NHibernate

9 lutego 2012, 12:18 dario Komentarze (2)

Używać czy nie używać Respository Pattern?

O to jest pytanie. Używać czy nie używać Repository Pattern? Ostatnio troszkę się nad tym zastanawiałem i doszedłem do solidnego wniosku: tak i nie. :)

Wiadomo, że ktoś zawsze powie: "to zależy". A ja mu przytaknę. W malutkim projekcie nie ma sensu. W większym? Chcąc być purystą powinienem zrealizować cały dostęp do danych poprzez wartwę realizowaną na bazie Repository Pattern. Czy to ma sens? Co jeśli chcę tylko wyciągnąć imię i nazwisko ostatnio dodanego użytkownika? Czy na prawdę muszę pisać te wszystkie interfejsy, implementacje dla tak prostego zapytania, którego testowanie ma nawet znikomy sens? Czy repozytoria z szeregiem metod w stylu:

  • FindBy
  • FindByEmail
  • FindByNIP
  • FindByNIPWithSpecialSomething

naprawdę są tworem wielokrotnego użytku? Z doświadczenia swojego i nie tylko wiem, że dana metoda bardzo rzadko jest wykorzystywana w więcej niż jednym miejscu w aplikacji.

Przeszukując czeluścia internetu dokopałem się do kilku fajnych spostrzeżeń. Podstawowym była zmiana podejścia samego Ayende co do stosowania IRepository<T>. Wcześniejsze moje doświadczenie w głównej mierze opiera się na tym co publikował Ayende, a to właśnie w publikowanym przez Niego Rhino.Commons znalazłem dosyć rozbudowany "Repository Pattern". Dosyć opasły jak teraz widzę, ale wcześniej mi to także nie przeszkadzało.

Repository Pattern ma teoretycznie pozwolić na odseparowanie się od warstwy dostępu do danych. Co więcej niektóre implementacje znalezione w necie mają pozwolić na "łatwą podmianę ORM'a". Huh...

Osobiście używam NHibernate od kilku lat i jak narazie nie widzę alternatywnego ORM'a, który spełniał by moje oczekiwania. Nie zamierzam podmieniać go "w locie". NHibernate oprócz generatora SQLa (bazowa funkcjonalność) posiada wiele innych funkcjonalności (zalet), od których nie powinno się odseparowywać.

Bardzo fajnie odpowiedział Procent na zadane pytanie w serwisie devpytania: "Repository pattern - czy używać?". Podoba mi się to podejście z jednym małym wyjątkiem. Chciałbym zachować bardzo minimalny poziom abstrakcji nad samą implementacją ISession. Dlaczego? Otóż obejrzyjcie definicję tego interfejsu. Jest... co tu dużo mówić... WIELKA. Większość metod jest niepotrzebna w normalnej pracy. Po co mi taka lista w IntelliSense? Ja potrzebuję zaledwie kilku metod: zbuduj querę, dodaj nowy obiekt, usuń obiekt, pobierz obiekt. Resztą opiekuje sie moja infrastruktura.

I tu dochodzę do swoich wniosków.

Po pierwsze nie chcę całkowitej separacji od wszystkich dobrodziejstw, które oferuje mi NHibernate. NHibernate to jest moja warstwa dostępu do danych i chcę do niej łatwo sięgać kiedy tylko potrzebuję bez dodatkowych i zbędnych warstw.

Po drugie mimo wszystko postanowiłem, że zostawię sobie generyczny Repository<T>, który będzie służył mi jedynie do tego, aby ukryć implementację opasłego ISession. Dodatkowo pozwoli mi na łatwie dodanie specyficznych funkcjonalności w mojej infrastrukturze.

No dobra, a co z zapytaniami, które już tak trywialne (jak opisany wyżej przykład) nie są? Dobrze by było, aby ich nie rozrzucać w kodzie gdzie popadnie. Tu z pomocą przychodzi Query Object Pattern.

... ale o tym w kolejnym wpisie. :)

Tagi:

NHibernate

13 października 2011, 00:50 dario Komentarze (0)

MultiConstraint w ASP.NET MVC

Definiując ścieżki w aplikacji ASP.NET MVC możemy zadać warunki (constraint) określające poprawność reguły dla parametru. Ja na przykład stosuję (między innymi) domyślnie constraint, który przepuszcza ścieżki pisane tylko małymi literami. Dzięki temu ustrzegam się przed zdublowanymi stronami indeksowanymi przez wyszukiwarki (wielkość liter ma znaczenie).

Niestety dla jednego parametru można zadać tylko jeden warunek naraz. A co jeśli chcemy dołożyć kolejny? Można sobie pomóc korzystając z jednego, który przyjmuje kolejne. Tak oto mamy MultiConstraint.

public class MultiConstraint : IRouteConstraint
{
    private readonly IRouteConstraint[] _contraints;

    public MultiConstraint(params IRouteConstraint[] contraints)
    {
        _contraints = contraints;
    }

    public bool Match(
        HttpContextBase httpContext,
        Route route,
        string parameterName,
        RouteValueDictionary values,
        RouteDirection routeDirection)
    {
        return _contraints.All(x => x.Match(
            httpContext,
            route,
            parameterName,
            values,
            routeDirection));
    }
}
Zasada działania jest prosta. W konstruktorze podajemy dowolną ilość constraintów. Jeśli wszystkie będą spełnione to całość także.

Prosty przykład wywołania dla parametru controller:

controller = new MultiConstraint(new LowercaseConstraint(), new SubdomainConstraint(portalDomain))
Taka mała rzecz, a cieszy, bo bardzo mi brakowało takiej możliwości.

Tagi:

Asp.net-mvc

19 sierpnia 2011, 22:29 dario Komentarze (0)


SSD dla programisty - tak!

Kilka dni temu pisałem, że podmieniłem dość szybki dysk Seagate 320GB 7200obr/min na SSD Vertex OCZ 2 120GB. Wyniki testu syntetycznego zwiastowały duuuże przyspieszenie.

Seagate

Vertex

Po kilku dniach pracy mogę śmiało polecić wszystkim przesiadkę na SSD. Komfort pracy zupełnie inny. Wszystko działa dużo szybciej. Kompilacja, publikowanie projektów pod VS2010 działa szubciutko. Samo studio dostało skrzydeł. Nie wspominam już o wszelkich pozostałych standardowych operacjach jakie wykonuje się podczas pracy z kompem. Otwieranie programów, nawet na zimnym starcie to jest ułamek sekundy.

Pisali ludzie, że niektóre Vertexy mają problemy i czasami zawieszają kompa. Mi takie coś się nie przytrafiło ani razu. System W7 64bit śmiga aż miło. Teraz w końcu mogę wyłączać komputer jak go nie używam dłuższy czas. Uruchomienie trwa kilka (dosłownie sekund) do stanu kiedy mogę włączać dowolne aplikacje. Wcześniej trwało to zbyt długo.

Na dysku SSD mam system, pliki projektów i reszta danych i programów, z których często korzystam w pracy (i nie tylko). Seagate wylądował w zewnętrznej obudowie i spięty po USB stanowi dodatkowy zbiornik na pozostałe, mniej używane dane.

Jeśli się zastanawiacie czy warto wydać te kilka stówek to powiadam Wam, że naprawdę warto :)

Tagi:

Sprzęt

O autorze

Dariusz Gil - projektant i programista aplikacji internetowych budowanych na platformie Microsoft w technologii ASP.NET (C#) oraz MS SQL Server. Obecnie właściciel (narazie :)) jednoosobowej firmy Softio.

Filtruj używając APML