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

Pingbacks and trackbacks (1)+

Dodaj komentarz




  Country flag
biuquote
  • Komentarz
  • Podgląd
Loading


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