Yhä useammin tietokantoja käsitellään ORMien kautta. Yhä vahvemmin olen myös alkanut ajatella, että ORMin piilottaminen jonkin lisäkerroksen taakse on huono idea. Kokosin neljä tapaa hakea dataa sekä niiden hyvät ja huonot puolet. Oletuksena on, että kaikissa vaihtoehdoissa käytetään kuitenkin ORMia joko suoraan tai piilossa.
Metodi per kysely
Hyvin yksinkertainen malli. Jokaiselle erilaiselle kyselylle on oma metodi.
``` csharp
public interface IProductDA
{
IEnumerable GetProductsByName(string name);
IEnumerable GetProductsByCategory(Guid categoryId);
}
```
Plussat
Ei sidoksia tiettyyn ORMiin
Voi optimoida jokaisen metodin
Miinukset
Missä tahansa vähänkin monimutkaisessa järjestelmässä metodeita tulee helposti satoja. Metodien joukosta on vaikea löytää se tietty ja sama toiminto voi jo löytyä hieman eri nimisen metodin takaa.
``` csharp
public interface IProductDA
{
IEnumerable GetProductsByName(string name);
// pari ehtoa + eagerloadia ja lopputulos on tämä
IEnumerable GetProductsByNameMaxPriceCategoryAndRatingIncludeCategoryAndReviews(string name, decimal maxPrice, Guid categoryId, decimal minRating);
// nimet eroavat, mutta lopputulos on sama
IEnumerable GetProductsByNameIncludeCategoryAndReviews(string name);
IEnumerable GetProductsByNameIncludeReviewsAndCategory(string name);
}
```
Uusi kysely pitää lisätä interfaceen ja toteuttavaan luokkaan, josta aiheutuu joko kaikkien kirjastoa käyttävien sovellusten päivittäminen tai useita eri versioita kirjastosta
Specification tai vastaava pattern
Kyselyt hoidetaan yhdellä tai muutamalla metodille, jotka ottavat vastaan "speksit" (ehdot, eager load, järjestäminen jne.)
``` csharp
public interface IProductDA
{
IEnumerable Get(IEnumerable> filters,
IEnumerable> includes,
IEnumerable> orderings);
}
```
Plussat
Ei sidoksia tiettyyn ORMiin
Uudet kyselyt eivät aiheuta samanlaista päivitystarvetta ydinkirjastoon kuin edellisessä vaihtoehdossa
Paljon koodia perusarkkitehtuurin kasaamiseen, tulkkaamiseen eri ORMeille sopivaksi ja speksien rakentamiseen
``` csharp
var filters = new List>();
if(!string.IsNullOrEmpty(productName))
{
filters.Add(Filter.For(p => p.Name.Contains(productName)));
}
if(maxPrice.HasValue)
{
var priceFilter = Filter.For(p => p.Price <= maxPrice);
if(filters.Any())
{
var filter = filters.Last();
filters.Remove(filter);
var andFilter = Filter.And(filter, priceFilter);
filters.Add(andFilter);
}
else
{
filters.Add(priceFilter);
}
}
var includes = new[]{ Include.For(p => p.Category) };
var ordering = new[]{ Order.Desc(p => p.Price), Order.Asc(p => p.Name) };
var products = productDA.Get(filters, includes, ordering);
```
Ryhmittely ja projektiot tosi hankalia
``` csharp
// väännäpäs tästä jotkin näpsäkät abstraktit speksit
session.Query()
.GroupBy(p => new
{
Year = p.Published.Year,
Month = p.Published.Month
})
.Select(g => new
{
Year = g.Key.Year,
Month = k.Key.Month,
PostCount = g.Count()
});
```
IQueryable
Simppeli wrapper, jolla dataa haetaan IQueryablen kautta.
``` csharp
public interface IDataAccess where T : Entity
{
void Create(T item);
void Get(Guid id);
void Update(T item);
void Delete(T item);
IQueryable All();
}
public interface IProductDA : IDataAccess
{
}
```
Plussat
Edelleen ei sidoksia ORMiin
Minimalistinen. Koodaamiseen menee pari minuuttia.
Käyttää LINQ:ta suoraan, joten kyselyjä voi rakentaa vapaasti ydinkirjastoa muuttamatta
Miinukset
Ei voi käyttää ORMien erityisominaisuuksia tai niille pitää väsätä abstraktiot
Eager load: NHibernatessa Fetch, Entity Frameworkissä Include
Future query
2nd level cache
Muut APIt (Esim. NHibernatessa Criteria, QueryOver, HQL sekä SQL ja Entity Frameworkissä Entity SQL ja SQL)
Eri ORMeilla voi olla erilainen tuki LINQ-operaatioille / muutenkin käyttäytyä eri tavoilla
ORMin käyttäminen suoraan
Plussat
Saa kaiken hyödyn ORMien ominaisuuksista
Ei tarvetta abstraktiokerrokselle
Miinukset
ORMin vaihto työlästä
Yksikkötestaus
Eräs peruste ORMin piilottamiselle on ollut yksikkötestauksen mahdollistaminen. Kuitenkin ainakin NHibernaten ISessionFactory ja ISession ovat helppoja mockattavia, samoin Entity Frameworkin DbContext.
Yhteenveto
Vaikka jokaisella vaihtoehdolla on puolensa, normitapauksissa suosisin niitä alhaalta ylöspäin. ORMin piilottaminen vaatii paljon ylimääräistä koodia, joka ei kuitenkaan tuo lisäarvoa. ORMin vaihto ei kuitenkaan tule tapahtumaan, joten siihenkin varautuminen on turhaa työtä, etenkin kun samalla menetetään kunkin ORMin erityisominaisuudet.