I have an experimental project that will likely evolve later with real specs and everything. With this greenfield of no intervention, I can spike away with NHibernate's hot-roddin' buddies: Fluent NHibernate and NHibernate.Linq.
Admittedly, I view NHibernate the way the monkey looked at the monolith in the movie 2001 : A Space Odyssey - a mixture of abject fear and wonderment. I have been flinging a lot of things at the "black-shiny" to no avail.
I want to implement the specifications pattern in Linq. The idea of having Linq specifications is very compelling. So off I toddled with my new bones in hand, ready to fling them with impunity at the "black-shiny".
I have an abstract User with value objects for a Person and Address. For this post, I will focus on the Person value object that contains name properties for first and last name. My end-users will want to be able to filter the User entity on those that contain a name part in the full name - a concatenation of first and last name.
Here's my code so far:
public class User : AbstractUser{
}
public abstract AbstractUser
{
public virtual Person Person {get; private set:}
}
public class Person{
public virtual string LastName {get; private set;}
public virtual string FirstName {get; private set;}
public Person (string firstName, string lastName){
FirstName = firstName;
LastName = lastName;
}
public string FullName{
get{
return string.Format("{0} {1}", FirstName, LastName);
}
}
}
OK, that's fine. Now, I need to map my User entity. Mapping is trivial using Standard Mapping in Fluent NHibernate. I will also include my Person value object as a convention, so I can re-use that again as needed.
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Component(x =>
x.Person, PersonConvention.MapPerson(""));
}
}
public class PersonConvention
{
public static Action<ComponentPart<Person>> MapPerson(string columnPrefix)
{
return p =>
{
p.Map(n => n.FirstName).WithLengthOf(50);
p.Map(n => n.LastName).WithLengthOf(50);
p.Map(n => n.MiddleInitial).WithLengthOf(1);
};
}
}
Looks good so far. Now, I am using NHibernate.Linq and Linq.Specifications. I might know a portion of Mr. Smith's name, but not how to spell it. I need to filter those Users by their full name with a Contains on the full name.
public class UserNameSpecification : QuerySpecification<User>
{
private readonly string _name;
public UserNameSpecification(string namePart)
{
_name = namePart;
}
public override Expression<Func<User, bool>> MatchingCriteria
{
get {
return u => u.Person.FullName.Contains(_name);
}
}
}
Now, I should be able to query for FullName, right? I mean: it's a method result of two properties, not a value. At least, that's what I thought at first.
When Linq.NHibernate transmogrifies the C# into HQL, it has no idea how to translate FullName into its component FirstName, LastName.
Hmm, I must have missed a step somewhere. NHibernate.Linq has no idea about the contents of my MatchingCriteria() method. For all it knows, FullName is a string property.
Wait a second... My FullName() method might as well be a string property!
So if I map FullName() to Fluent NHibernate - Sure, it becomes a column. But, I can control the contents in my domain. I can now persist the method property to my persistence mechanism (DDD-speak for database, in this instance) and use it for querying later.
Here's the updated PersonConvention
public class PersonConvention
{
public static Action<ComponentPart<Person>> MapPerson(string columnPrefix)
{
return p =>
{
p.Map(n => n.FirstName).WithLengthOf(50);
p.Map(n => n.LastName).WithLengthOf(50);
p.Map(n => n.MiddleInitial).WithLengthOf(1);
p.Map(n => n.FullName).SetAttribute("access","readonly");
};
}
}
In my Setup method, I have inserted a "first user". I have FullName marked with the readonly attribute. Now, I can use the FindAll and pass in my specification.
[Test]
public void can_find_by_full_name()
{
IQueryable<User> byfullname = userRepository.FindAll(new UserNameSpecification("first user"));
Assert.AreEqual(1, byfullname.Count());
}
It passes. By the way, my repository's FindAll with the Linq.Specification implementation is as follows:
public IQueryable<TResult> FindAll<TResult>(ISpecification<User, TResult> specification)
{
return specification.SatisfyingElementsFrom(FindAll());
}
Keep in mind that this is not the end-game for NHibernate. AST to HQL parser projects appear to be in the works. Hopefully, we will be able to pass the contents of a method to NHibernate parse that to HQL for us. But until that is complete, we have the option of persisting method property results that NHibernate.Linq can parse.
Special thanks on the resolution of this issue goes to Hudson Akridge for pointing me in the right direction. Also, I would be remiss if I did not extend thanks to Steven Burman, the Mostly Clean Coder. Steven's posts on Linq.Specifications is an excellent resource for his project. He was also very kind to assist on a question I had. Thanks again, guys.