The three solutions available so far
1.The Query Object pattern was described by Martin Fowler. I like its short definition:
An object that represents a database query.What I don’t like so much is its long description:
A Query Object is an interpreter [Gang of Four], that is, a structure of objects that can form itself into a SQL query.A good example of Query Object is our dear ICriteria with all its classes. The problem is that I can’t expose neither ICriteria nor a concrete class using it because I don’t want expose nothing using NHibernate.
2.
Another possible solution is the Specifications Pattern (by Eric Evans and Martin Fowler).
Specification is again very powerful especially because easy to test. In .NET we have various implementations/interpretations based on LINQ. The advantage is basically that you can test your specification and your specification-composition in RAM… sure… hoping for that your LINQ provider can then efficiently translate it in SQL.
3.
The last is a repository implementing IQueriable (as proposed here) with its pro/cons:
- Easy to test.
- LINQ queries, for persistence, wrote every where.
- Hope that your LINQ provider can then efficiently translate it in SQL.
The fact
In NHibernate we have at least six ways to query our persistence (have a look to this post). I can choose the query-system that best fit my needs of balance between code-readability, code-maintainability and query-performance.Why I should limit myself in using just one ?
What I need is a solution to avoid the continuous modification of the repository’s interface maintaining the advantage NHibernate gives to me.
Enhanced Query Object
An object that represent a query. The responsibility of a EQO is returns the query results.In general you will express a EQO as an interface placed in the same assembly where you are putting your IRepository interfaces. An example could look like:
public interface IContactByNameQuery
{
string PartialFirstNameOrLastName { get; set; }
IPagedResults<Contact> GetAll(int pageSize, int pageNumber);
}
{
string PartialFirstNameOrLastName { get; set; }
IPagedResults<Contact> GetAll(int pageSize, int pageNumber);
}
The implementation of the above EQO, for NHibernate, could look as:
public class ContactByNameQuery : IContactByNameQuery
{
private readonly ISessionFactory sessionFactory;
public ContactByNameQuery(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
#region IContactByNameQuery Members
public string PartialFirstNameOrLastName { get; set; }
public IPagedResults<Contact> GetAll(int pageSize, int pageNumber)
{
var query = sessionFactory.GetCurrentSession().QueryOver<Contact>();
if (!string.IsNullOrWhiteSpace(PartialFirstNameOrLastName))
{
query.Where(
Restrictions.On<Contact>(c => c.FirstName).IsLike(PartialFirstNameOrLastName, MatchMode.Anywhere) |
Restrictions.On<Contact>(c => c.LastName).IsLike(PartialFirstNameOrLastName, MatchMode.Anywhere)
);
}
query.OrderBy(c => c.LastName).Asc.ThenBy(c => c.FirstName);
var queryCount = query.ToRowCountQuery();
var contacts = query.Take(pageSize).Skip((pageNumber -1) * pageSize).Future<Contact>();
var count = queryCount.FutureValue<int>().Value;
return new PagedResults<Contact>(count, contacts);
}
#endregion
}
{
private readonly ISessionFactory sessionFactory;
public ContactByNameQuery(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
#region IContactByNameQuery Members
public string PartialFirstNameOrLastName { get; set; }
public IPagedResults<Contact> GetAll(int pageSize, int pageNumber)
{
var query = sessionFactory.GetCurrentSession().QueryOver<Contact>();
if (!string.IsNullOrWhiteSpace(PartialFirstNameOrLastName))
{
query.Where(
Restrictions.On<Contact>(c => c.FirstName).IsLike(PartialFirstNameOrLastName, MatchMode.Anywhere) |
Restrictions.On<Contact>(c => c.LastName).IsLike(PartialFirstNameOrLastName, MatchMode.Anywhere)
);
}
query.OrderBy(c => c.LastName).Asc.ThenBy(c => c.FirstName);
var queryCount = query.ToRowCountQuery();
var contacts = query.Take(pageSize).Skip((pageNumber -1) * pageSize).Future<Contact>();
var count = queryCount.FutureValue<int>().Value;
return new PagedResults<Contact>(count, contacts);
}
#endregion
}
but you are completely free to implement it using LINQ, HQL, Criteria, H-SQL, SQL, NamedQuery or whatever you feel comfortable with.
How create a concrete instance of EQO ?
Using exactly the same identical way you are using for your concrete implementation of Repositories or DAOs implementations.
Try it and let me know how you feel.
I like it very much.
ReplyDeleteIt is the best way to abstract this concept.
I have seem many unsecsuffull attempts to abstract in a generic way the criteria api and even hql!.. On the other hand it is hard to test the code when you have Linq spread all over.
Excelent! It is a great way of abstract de problem of touch DAOs, Repositories and a clean strategy for organize all queries.
ReplyDeleteBy using a EQO based on Properties, perhaps the GetAll method will become very long and unreadable. What I'm doing is something like that but using Fluent interfaces, something like:
ReplyDeletecontactSearcher.NewSearch().ByName(partialName).GetResults();
The searcher in the NewSearch method creates a search class for that entity (a wrapper for the Criteria, HQL, etc..), then injects the session wrapper and then in the search class's fluent method, you add the restrictions.
The ByName method would be something like:
public IContactSearch ByName(string partialName){
criteria.Add(Restrictions.Like("Name", partialName, MatchMode.Start));
...
return this;
}
You can use the same approach to page results you use in your example.
What do you think?
@Lenny
ReplyDeleteperhaps to much work to do...
You are defining your DSL, you have to define joins, order, OR and so on... seems a Criteria over Criteria.
If you look at your system you will see that you end in certain query for specific use-case, in some cases you will have better results using MultiQuery/MultiCriteria... I'm feeling better with one-class, one-responsibility, one-resultset.
Seems fair enough... Sometimes my approach goes well, but as a query gets very specific, your approach may suit better, I'll give it a try.
ReplyDeleteThanks!
Cool post. Do you have a download sample?
ReplyDelete@KJ
ReplyDeletewhat you need exactly ?
I'm asking because the code of the example is already in the post.
Is there a mail id i can reach you offline?
ReplyDelete@id
ReplyDeletesure, namesurname @ gmail.com
Nice, now we just need repositories to add objects to the database.
ReplyDelete@Carlos Peix
ReplyDeleteYou can avoid the usage of plurals talking about "repository".
E vero!
ReplyDeleteIsn't this just a specialized repository? The fact that the implementation resides in different classes is, well, an implementation detail and you could already achieve the same by applying the interface segregation principle by still having a single repository class.
ReplyDeleteThere was others candidate names... one of those was "micro-repository".
ReplyDeletebtw I found EQO as a good enough name basically because it match a big part of the Fowler's definition.
I came up with the same type of solution and i even went as far to create a framework for caching my individual EQO results. simply decorate the class with for example [CacheQuery(Hours = 1)] and transparent caching takes place.
ReplyDeleteNhibernate can do itfor you ;)
ReplyDeletedoes it do it only for full entities or projections as well?
ReplyDeletefor everything you want/need.
ReplyDelete