Na obsah stránky

Doménové dotazy

Aleš Roubíček | | # permalink

V každé aplikaci, kde se pracuje s daty, se dostaneme do situace, kdy se nad daty potřebujeme dotazovat. Pokud přijmeme za svůj vzor Repository, můžeme se dostat do situace, podobné té v následující ukázce:

public interface IArticlesRepository {
  Article FindById(long id);
  IEnumerable<Article> FindAll(PagingInfo paging);
  IEnumerable<Article> FindByCategory(Category category, PagingInfo paging);
  IEnumerable<Article> FindByAuthor(User author, PagingInfo paging);
  IEnumerable<Article> FindByTag(string tag, PagingInfo paging);
}

Jak vidno s každým typem dotazu, rozšiřujeme rozhraní naší repository. Což porušuje Open/closed principle – jeden z pilířů Solidní architektury. Ten nám říká, že objekt by měl být otevřený k rozšíření, ale uzavřený k modifikaci. Přidáváním dalších metod ho očividně rozšiřujeme. Jak na to?

Jedním z možných řešení je zavedení dotazovacích objektů, kde využijeme kompozice k docílení lepšího návrhu. A protože jde o obecný mechanismus, můžeme ho pěkně generalizovat. Dále budu předpokládat, že pracujeme vůči datovému zdroji podporujícímu LINQ dotazování, jako je NHibernate.Linq, LINQ2SQL nebo EntityFramework.

public interface IQueryObject<T> {
  int Count(IQueryable<T> table);
  IEnumerable<T> Fetch(IQueryable<T> table);
  T FetchOne(IQueryable<T> table);
}

Dotazovací objekt má tři metody. První spočítá kolik objektů splňuje podmínky dotazu. Druhá vrátí kolekci vybraných objektů a poslední vrací jedinečný záznam. K takovému rozhraní si můžeme vytvořit bázovou třídu, která nám pomůže s rychlejším vytvářením konkrétních dotazů.

public abstract class QueryObjectBase<T> : IQueryObject<T> {

  protected QueryObjectBase<T>(PagingInfo paging) {
    Paging = paging;
  }

  public PagingInfo Paging { get; private set; }

  protected abstract IQueryable<T> CreateQuery(IQueryable<T> table);

  public virtual int Count(IQueryable<T> table) {
    return CreateQuery(table).Count();
  }

  public virtual IEnumerable<T> Fetch(IQueryable<T> table) {
    return CreateQuery(table).
      Skip(Paging.Skip).
      Take(Paging.PageSize).
      ToArray();
  }

  public virtual T FetchOne(IQueryable<T> table) {
    return CreateQuery(table).FirstOrDefault();
  }
}

Konkrétní dotazovací objekt pak ve většině případů implementuje jedinou metodu, která vrací LINQ dotaz. V případě potřeby si však konkrétní implementace může připravené metody změnit k obrazu svému. Máme zde zavedenou i konvenci stránkování, která předchází generování neomezených dotazů do databáze. A teď už nám zbývá jen zabalit to pěkně do nějakého DAO objektu.

public interface IQueryExecutor<T> {
  int Count(IQueryObject<T> query);
  IEnumerable<T> Fetch(IQueryObject<T> query);
  T FetchOne(IQueryObject<T> query);
}

DAO objekt jediný zná konkrétní implementaci datového uložiště, kterou předává do dotazovacího objektu jako IQueryable<T>. Výsledné použití v praxi může pak vypadat následovně:

var articles = _articlesDao.Fetch(new CategoryArticlesQuery(category, pagingInfo));

Kde konkrétní implementace dotazovacího objektu vypadá takto:

public class CategoryArticlesQuery : QueryObjectBase<Article> {

  public CategoryArticlesQuery(Category category, PagingInfo paging)
    : base(paging) {
    Category = category;
  }

  public Category Category { get; private set; }

  protected override IQueryble<Article> CreateQuery(IQueryable<Article> table) {
    return from article in table
           where article.Category == Category
              && article.IsPublished
           orderby article.PublishDate descending
           select article;
  }
}

Výhodou i je, že pokud potřebujeme upravit dotaz, děláme to vždy jen jednou na jednom místě.

Našli jste v článku chybu? Máte námět na reportáž? Založte mi ticket.

Reakce v síti

Líbilo se

  • Načítají se data…

Přeposláno dál

  • Načítají se data…

Komentáře

Okomentováno