June 3, 2008 by
ryan
Settings
Up until now, we've been using NHibernate Query Generator for all of our data access. Although this is a great way to retrieve our data, there is another option we can play around with -- LINQ for NHibernate. To set this up in our existing application (see Part 1, Part 2, Part 3 and Part 4 on creating the ASP.NET MVC Application) we'll first need to grab the code out of subversion https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/experiments/NHibernate.Linq/ and build it using MSBuild or Visual Studio. After that we want to add a reference to it in our application.
Simple Code
Next we'll want to update our controller to use Linq for NHibernate instead of NHQG (Service layer would be better place for this type of code but since this is a demo it'll be okay -- for more on using a service layer to handle all the repository code check out Michael Hanney's post on ActiveRecord, NHibernate and ASP.NET MVC). The initial NHGQ code is:
var p = Repository<Product>.FindOne(Where.Product.Title == ID);
Our LINQ for NHibernate query will look like this:
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
where item.Title == ID
select item).First();
It's pretty obvious that the Linq code is a bit longer than the NHQG code. Although that in itself is not a bad thing, it may turn some people away. Momentarily, we'll see some scenarios where Linq for NH is very useful.
Paging and Sorting
One nice thing we can easily do with Linq for NHibernate is page and sort our data. If we simply want to get a list of all products it would look like this.
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
select item
).ToList()
To page/sort the data it's just a slight addition to the list all code.
int itemsPerPage = 5;
int startIndex = (ID.Value - 1)* itemsPerPage;
var p = (from item in UnitOfWork.CurrentSession.Linq<Product>()
orderby item.Title ascending
select item
).Skip(startIndex).Take(itemsPerPage).ToList();
More Advanced Usage
Kyle Baley's article on Linq for Nhibernate shows a more interesting use for Linq for NHibernate; we can create a generic method that adds query criteria on the fly. This would make our code much more reusable so we're going to go ahead and make a demo class heavily based on these concepts.
public class QueryHandler<T>
{
private IList<linqExpression.Expression<Func<T, bool>>> _criteria;
public QueryHandler()
{
_criteria = new List<linqExpression.Expression<Func<T, bool>>>();
}
public void AddCriteria(linqExpression.Expression<Func<T, bool>> LambdaFunc)
{
_criteria.Add(LambdaFunc);
}
public IList<T> GetList()
{
var query = from item in UnitOfWork.CurrentSession.Linq<T>()
select item;
//Tack on our query Criteria
foreach (var criterion in _criteria)
{
query = query.Where<T>(criterion);
}
return query.ToList();
}
}
Here, we've created a class that has a private list of criteria, a method to add criteria to the list and a method to get the list based on the given criteria. I realize it may be a little intimidating but we can perfom most of our select queries through this method due to the use of Generics.
Updating the controllers to use this functionality is not too difficult. For pages that simply retrieve lists we call the GetList method without specifying any criteria:
var queryHandler = new QueryHandler<Product>();
var p = queryHandler.GetList().Skip(startIndex).Take(itemsPerPage).ToList();
Pass in new lambda expressions to add query criteria
var queryHandler = new QueryHandler<Product>();
queryHandler.AddCriteria(item => item.Title == ID);
var p = queryHandler.GetList().First();
Now we see there are multiple options for interacting with our ActiveRecord Repository. Please let me know of any changes that you would make. I've updated the demo code in Assembla -- http://svn2.assembla.com/svn/NHibernateTest - Standard disclaimer does apply (some of the code is less than ideal but for learning it should be okay).
May 22, 2008 by
ryan
We're going to keep moving now that everything is setup (see part one for setup) and configured (see part two for configuration).
First off we are going to create our classes. The original classes and SQL tables are posted below (these may look familiar -- they are from my initial NHibernate post)
Initial Product
public class Product
{
private IList<Product> _RelatedProducts;
private IList<ProductGroup> _ProductGroups;
public Product()
{
_RelatedProducts = new List<Product>();
_ProductGroups = new List<ProductGroup>();
}
public virtual string ID { get; private set; }
public virtual string Title { get; set; }
public virtual string ImagePath { get; set; }
public virtual string Description { get; set; }
public virtual IList<Product> RelatedProducts
{
get { return _RelatedProducts; }
set { _RelatedProducts = value; }
}
public virtual IList<ProductGroup> ProductGroups
{
get { return _ProductGroups; }
set { _ProductGroups = value; }
}
}
Initial ProductGroup
public class ProductGroup
{
public virtual string ProductGroupID { get; set; }
public virtual string Title { get; set; }
public virtual IList<Product> Products { get; set; }
}
SQL Tables
CREATE TABLE [dbo].[SimpleProducts](
[ProductID] [char](32) NOT NULL,
[Title] [nvarchar](50) NOT NULL,
[ImagePath] [nvarchar](300) NULL,
[Description] [nvarchar](500) NULL
)
CREATE TABLE [dbo].[RelatedProductsLookup](
[ProductID] [char](32) NOT NULL,
[RelatedProductID] [char](32) NOT NULL
)
CREATE TABLE [dbo].[ProductsProductGroupsLookup](
[ProductGroupID] [char](32) NULL,
[ProductID] [char](32) NULL
)
CREATE TABLE [dbo].[ProductGroups](
[ProductGroupID] [char](32) NOT NULL,
[Title] [nvarchar](50) NULL
ActiveRecord Classes
In a traditional NHibernate application, we would write usually our mapping files at this time(see my other NHibernate post for more on that). Since we're using the ActiveRecord pattern, however, we can specify all our mappings inline with the classes. It is important to note that this would not be a pure domain because we're placing our mappings inside the model. Warning if you're sensitive to using Attributes this may not be the code for you...
Our classes will now begin with an ActiveRecord attribute over the class; our properties will begin with Property/HasAndBelongsToMany/etc. Please note, for the sake of the example, I'm being extremely verbose with my attributes. If your table/column names match the class/property names, some of the additional info in the attribute is not necessary.
[ActiveRecord(Table="SimpleProducts")]
public class Product
{
private IList<Product> _RelatedProducts;
private IList<ProductGroup> _ProductGroups;
public Product()
{
_RelatedProducts = new List<Product>();
_ProductGroups = new List<ProductGroup>();
}
[PrimaryKey(Column="ProductID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]
public virtual string ID { get; private set; }
[Property(NotNull=true, Length=50, Column="Title")]
public virtual string Title {get; set; }
[Property(Length=300, NotNull=false, Column="ImagePath")]
public virtual string ImagePath { get; set; }
[Property(NotNull = false, Length = 500, Column="Description")]
public virtual string Description { get; set; }
[HasAndBelongsToMany(Table="RelatedProductsLookup", ColumnKey="ProductID", ColumnRef="RelatedProductID")]
public virtual IList<Product> RelatedProducts
{
get { return _RelatedProducts; }
set { _RelatedProducts = value; }
}
[HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductID", ColumnRef="ProductGroupID")]
public virtual IList<ProductGroup> ProductGroups
{
get { return _ProductGroups; }
set { _ProductGroups = value; }
}
}
[ActiveRecord(Table="ProductGroups")]
public class ProductGroup
{
[PrimaryKey(Column="ProductGroupID", Generator=Castle.ActiveRecord.PrimaryKeyType.UuidHex)]
public virtual string ProductGroupID { get; set; }
[Property(NotNull=true, Length=50, Column="Title")]
public virtual string Title { get; set; }
[HasAndBelongsToMany(Table="ProductsProductGroupsLookup", ColumnKey="ProductGroupID", ColumnRef="ProductID")]
public virtual IList<Product> Products { get; set; }
}
Repository Object
Another added benefit of using the Castle Active Record library is that we can use the Repository<T> for all of our object persistence. Instead of creating our own implementation of IRepository, we can write code like this to save / retrieve / update objects.
Selecting an object (our product IDs are HEX UUID's so this is not exactly accurate)
var p = Repository<Product>.Get(23);
Saving / Updating
Repository<Product>.Save(p);
The repository is pretty nice -- we can save all of our objects outside of the domain, which makes for a much cleaner design. Next time, we'll be looking at the extremely simple MVC application powered by this model and NHibernate Query Generator. Continue to Part 4