Usually I'd put my criterias/hql queries in a repository/dal class related to the entity, but lately I'be been thinking of adding another abstraction that represents what a query is, this would give me the possibility of adding common behavior to all queries (e.g. pagination) in a base class, etc.
so these are my components now;
generic interface not related to nhibernate:
public interface IQuery<T>
{
IList<T> List();
T Single();
}
Example implementation of a Criteria based query, something similar could be done with an Hql query, or a nhibernate-linq query
public abstract class CriteriaQuery<T>: IQuery<T>
{
[Inject]
public ISessionFactory SessionFactory { protected get; set; }
protected ISession Session
{
get { return SessionFactory.GetCurrentSession(); }
}
protected abstract ICriteria Configure(ICriteria criteria);
[Transaction]
public virtual IList<T> List()
{
var criteria = Session.CreateCriteria(typeof (T));
return Configure(criteria)
.List<T>();
}
[Transaction]
public virtual T Single()
{
return Configure(Session.CreateCriteria(typeof(T)))
.UniqueResult<T>();
}
}
and here a domain specific query would look like:
public interface IGetVideosQuery: IQuery<Video>
{
IGetVideosQuery Page(int index);
IGetVideosQuery PageSize(int pageSize);
IGetVideosQuery AllTime { get; }
IGetVideosQuery Today { get; }
IGetVideosQuery LastWeek { get; }
}
any thoughts on this? possible problems you see I might come across? Thanks!
My first suggestion would be to go with Linq2NH, available in NHibernate 2.1 and beyond. The Session exposes an
AsQueryable<T>()
method on which you can build a Linq query. That will allow you to attach criteria and projections as you please using standard Linq extension methods, including Skip() and Take() to implement pagination (myQuery.Skip(PageIdx*PageSize).Take(PageSize).ToList()
), and Where clauses to filter. The lambdas and variables used in those queries could be encapsulated in some sort of QueryInfo class, allowing repeatable, persistable queries.I took a different path, that of CQS. What this does is that it separates my mutating logic from my query logic.
Now, there are different ideas on how to implement this, and I choose this one:
All my mutation logic is activated using command like
DeactivateUser
andChangeRelationAddress
. For this business logic, I have normal repositories like you describe;For displaying data, I use a completely managed system. With this system, I describe queries like the Specification Pattern. I describes the base table and the fields. The query system automatically creates the joins for me and with filter definitions I provide filters.
This system allows me to keep the complexity of my repositories down because I do not have to think about filters a user may set or
ORDER BY
's. The system that displays this data automatically creates the filters usingCriteria
and handles paging for me.Maybe such a system can work for you.