V architektuře naší MVC aplikace jsme se dostali již poměrně daleko. Vybrali jsme si šablonovací systém, máme vyřešenou integraci s IoC kontejnerem, vymazlené routování, připravené model bindery a teď je na čase nějakým způsobem model persistovat. Existuje mnoho způsobů, jak toho dosáhnout.
- Persistenci si můžete řešit sami na úrovni ADO.NET. Jenže proč řešit něco, co už někdo stokrát vyřešil?
- Použít nějaký ORM nástroj.
ORM nástrojů na výběr máme dnes tolik, že je stěží na prstech spočítáme, jaký si tedy vybrat? Pro mnohé bude první volbou LINQ to SQL. Pro jednoduché mapování (1:1) a tam, kde je možné být závislý pouze na MS SQL, je toto řešení dostačující. Osobně preferuju malinko složitější cestu, ale ve výsledku myslím, že mnohem mocnější. :)
Castle ActiveRecord a Repository pattern
Já ve svých posledních proktech používám Castle ActiveRecord, což je implemenatce návrhového vzoru ActiveRecord nad ORM frameworkem NHibernate. V mnoha ohledech práci s NHibernatem značně zjednodušuje, ať už je to mapování, automatickou správu session a podobně. Způsoby užití jsme už popsal ve spotu ActiveRecord vs. Repository pattern. Další výhodou je, že vše, co funguje v NHibernate (LINQ, Spatial, vlastní mapovací typy), můžete použí i s Castle ActiveRecord.
Datové třídy však nedědím z bázové třídy
ActiveRecordBase, ale používám Repository, která se stará
o CRUD operace
s entitou. Pokud chcete mít mapování a závislost na ActiveRecord snadno
odstranitelnou, můžete mapovací atributy vyčlenit třeba do samostatného
partial souboru. Ale nejspíš nikdy v životním cyklu vaší
aplikace nebudete ORM framework měnit, takže se trápit metadaty
o databázovém schema je celkem zbytečné…
Základem pro repository je generické rozhraní
IRepository<>, pro snadnou automatickou registraci
repositoří v kontejneru ještě nadefinujeme ještě i negenerické
rozhraní:
using Castle.ActiveRecord;
public interface IRepository {}
public interface IRepository<Tentity> : IRepository where TEntity : class {
TEntity FindById(int id);
void Save(TEntity entity);
void Delete(TEntity entity);
}
public abstract class ActiveRecordRepositoryBase<TEntity>
: IRepository<TEntity> where TEntity : class {
public virtual TEntity FindById(int id) {
return ActiveRecordMediator<TEntity>.FindByPrimaryKey(id);
}
public virtual void Save(TEntity entity) {
ActiveRecordMediator<TEntity>.SaveAndFlush(entity);
}
public virtual void Delete(TEntity entity) {
ActiveRecordMediator<TEntity>.DeleteAndFlush(entity);
}
}
Pojďme si podporu pro ActiveRecord integrovat do naší MVC aplikace.
Integrace přes Binsor
Výhodou projektů z Castle, je, že se mezi sebou dokážou integrovat a
využívat navzájem výhod své synergie. Nejprve si do projektu přidáme
referenci na knihovnu Castle.ActiveRecord a ještě
Castle.Facilities.ActiveRecordIntegration. Následně
zaregistrujeme v souboru Windsor.boo facility pro integraci
ActiveRecordu do IoC kontejneru:
import System.Reflection
// podpora ActiveRecord v kontejneru
facility ActiveRecordFacility:
configuration:
@isWeb = true, isDebug = false
assemblies = [ Assembly.Load("MyApplication.Entities") ]
config(keyvalues, item: add):
show_sql = true
command_timeout = 500
dialect = 'NHibernate.Dialect.MsSql2005Dialect'
connection.provider = 'NHibernate.Connection.DriverConnectionProvider'
connection.driver_class = 'NHibernate.Driver.SqlClientDriver'
connection.connection_string_name = 'CONN_DEMO'
Teď ještě potřebujeme ve web.configu přidat
ConnectionString pod klíčem CONN_DEMO a volitelně
přidat životnost session na jeden request. To uděláte tak, že do sekce
system.web\httpModules přidáte následující modul:
<add
name="PerRequestSessionScope"
type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord"/>
To má za výhodu to, že všechny vaše dotazy za jeden request se chovají jako jedna transakce. Takže integrace frameworku, je hotová. Teď ještě přidáme automatickou registraci samotných repositoří.
for repository in AllTypesBased of IRepository("MyApplication.Entities"):
component repository.Name.ToLower(), repository.GetInterfaces()[0], repository:
lifestyle PerWebRequest
Tímto se zaregistrují všechny repository, které dědí z
ActiveRecordRepositoryBase a jsou pak ve vašich komponentách
dostupné jako IRepository<User> (příklad pro entitu
User). Pokud budete mít v repository více metod, je dobré
vytvořit pro ně kontrakt, který obsahuje kontrakt generické repository.
Například takto:
public interface IUsersRepository : IRepository<User> {
User FindUserByEmail(string email);
}
Registrace takovýchto komponent už je o něco složitější, ale dá se
to také zvládnout. Nejprve si zavedeme konvenci, že repository, které
takovéto kontrakty implementují, se budou jmenovat až na
I na začátku stejně. :) Takže pro kontrakt
IUsersRepository budeme mít konkrétní implementaci
UsersRepository atd. Pro jejich registraci využijeme metodu
System.Type.FindInterfaces, která bere jako první argument
delegát na vyhledávací funkci a druhý argument jsou hledací kritéria.
Takže si vytvoříme funkci, která splňuje kontrakt delegáta, a následně
ji hned využijeme pro registraci:
def contains_name(t as Type, o as Object):
return t.Name.Contains(o.ToString())
for repository in AllTypesBased of IRepository("MyApplication.Entities"):
repository_interface = repository.FindInterfaces(contains_name, repository.Name)
component repository.Name.ToLower(), repository_interface, repository:
lifestyle PerWebRequest
Cool ne? Pár řádků konfigurace a nejspíš máme vystaráno, tedy když dodržíme stanovenou konvenci, tak už se o registraci komponent datové vrstvy nemusíme starat. Stejně tak už máme zaregistrované model bindery a controllery. Trošku nám to konfigurace povyrostla, ale díky možnostem, které nám přináší Binsor, je to jen zlomek toho, co by bylo potřeba napsat v XML.
Projektík se nám pomalu rozrůstá, sice stále nic nedělá, ale je to dobrý základ pro pořádný web. Pokusím se ho hodit někam do SVN, aby bylo lehčí se v projektu orientovat a byly vidět jednotlivé změny.













