Wednesday, 29 July 2009

NHibernate N:N relationships

NHibernate provides the facility to map out many-to-many relationships without the need for the intersection table to be represented in the object model. Take for example, the following schema of Users, Roles and the intersection table that links users to many roles and roles to many users;

image

We then have the following object model to represent this;

image

or, in code if you prefer;

public class User
{
    // Fields
    public virtual Guid Id { get; set; }
    public virtual string UserName { get; set; }
    public virtual string Email { get; set; }

    // Storage for the roles that this user belongs to
    private readonly IList<Role> _roles = new List<Role>();

    // Accessor to list of roles - note we don't return list
    // that we can add/remove from - we use methods to do that
    // so we can implement any business logic.
    public virtual ReadOnlyCollection<Role> Roles
    {
        get 
        { 
            return new ReadOnlyCollection<Role>(_roles);
        }
    }

    // Method to associate a role with this user
    public virtual void AddRole( Role role )
    {
        _roles.Add( role );
    }

    // Method to remove a role from this user
    public virtual void RemoveRole( Role role )
    {
        _roles.Remove(role);
    }
}
public class Role
{
    // Fields
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }

    // Storage for the users that have this role
    private readonly IList<User> _users = new List<User>();

    // Returns list of users belonging to this role
    // Notice this can't be added or removed from.
    // Nor is there any logic to add users into roles
    // as the link is maintained from the User object.
    public virtual ReadOnlyCollection<User> Users
    {
        get
        {
            return new ReadOnlyCollection<User>(_users);
        }
    }
}

Notice that the relationship between users and roles is maintained and owned in the user object, not role. The role object just gives us information about which users belong to a role, but we can’t add or remove users into it without loading the user and accessing it’s Add/Remove Role methods. We can map this out as follows (notice, I’m using Fluent mappings for NHibernate here);

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        base.Id(x => x.Id).GeneratedBy.GuidComb();
        base.Map(x => x.UserName);
        base.Map(x => x.Email);

        HasManyToMany<Role>(Reveal.Property<User>("_roles"))
            .LazyLoad()
            .AsBag()
            .WithTableName("UserRoles")
            .WithParentKeyColumn("UserId")
            .WithChildKeyColumn("RoleId")
            .Access.AsReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
            .Cascade.All();
    }
}

public class RoleMap : ClassMap<Role>
{
    public RoleMap()
    {
        base.Id(x => x.Id).GeneratedBy.GuidComb();
        base.Map(x => x.Name);

        HasManyToMany<User>(Reveal.Property<Role>("_users"))
            .LazyLoad()
            .AsBag().Inverse()
            .WithTableName("UserRoles")
            .WithParentKeyColumn("RoleId")
            .WithChildKeyColumn("UserId")
            .Cascade.None();
    }
}

Notice that the role map has .Inverse() set, which indicates that it is not the owner of the relationship.

That is pretty much all there is to it, NHibernate will take care of maintaining the relationship table for you.

Tuesday, 28 July 2009

Query NHibernate – Simple query patterns

There are a variety of ways to query NHibernate, including using HQL, the criteria API, Linq2NH, and the criteria API tied up with the Lambda extensions. My preference is the criteria API as it’s a powerful way of expressing dynamic queries. I’m going to explore how we go about implementing some of the more basic query patterns using this API;

Given the following database as an example, where a location can be split into multiple sub-locations, each sub-location belonging to a cost centre, we’ll drive out some simple queries;

 image

First off, a simple location query by name;

ICriteria c =
    session.CreateCriteria(typeof(Location))
    .Add(Restrictions.Eq("Name", "Manchester"));

Next, a query that will check the name contains particular text and the postcode starts with text provided.

ICriteria c =
    session.CreateCriteria(typeof(Location))
    .Add( Restrictions.Conjunction()
        .Add(Restrictions.Like("Name", "%manchester%"))
        .Add(Restrictions.Like("Postcode", "M60%")));

How about looking for all sub locations where it’s parent location has a postcode of M60*?

ICriteria c = session.CreateCriteria(typeof(SubLocation))
    .CreateAlias("Location", "l")
    .Add(Restrictions.Like("l.Postcode", "M60%"));

Going further, how about finding all sub locations where some text can be found either in the sub location name, the location name, location address, location postcode or cost centre name? Simples….

ICriteria criteria = session.CreateCriteria(typeof(SubLocation))
    .CreateAlias("Location", "l")
    .CreateAlias("CostCentre", "cc")
    .Add(Restrictions.Disjunction()
        .Add(Restrictions.Like("Name", freeTextToMatch))
        .Add(Restrictions.Like("l.Name", freeTextToMatch))
        .Add(Restrictions.Like("l.Address", freeTextToMatch))
        .Add(Restrictions.Like("l.Postcode", freeTextToMatch))
        .Add(Restrictions.Like("cc.Name", freeTextToMatch)));

What about finding all locations that have more than one sub location?

DetachedCriteria subquery = DetachedCriteria.For(typeof(SubLocation))
   .Add(Restrictions.EqProperty("l.Id", "Location.Id"))
    .SetProjection(Projections.Count("Id"));

ICriteria criteria = session.CreateCriteria(typeof(Location), "l")
    .Add(Subqueries.Lt(1, subquery));

It’s a bit more complicated, but the first bit creates a reusable query that will look for sub locations where the sub location’s parent location Id is of the same value as the Id property of the aliased object “l” passed into the query. It then projects the results into a count against the Id property.

The second part then creates a criteria that will find all locations, aliased as “l” (for the sub query above), and execute the given subquery, ensuring that the result is > 1.

Finally, getting a bit more complex, find all locations having more than one sub location in cost centre named “ENGINEERING”?

DetachedCriteria subquery = DetachedCriteria.For(typeof(SubLocation))
        .CreateAlias("CostCentre", "cc")
        .Add(Restrictions.EqProperty("l.Id", "Location.Id"))
        .Add(Restrictions.Eq("cc.Name", "ENGINEERING"))
        .SetProjection(Projections.Count("Id"));

ICriteria criteria = session.CreateCriteria(typeof(Location), "l")
        .Add(Subqueries.Lt(1, subquery));

Rhino – Mock versus Stub

Questions always pop up on when we should use mocks and when we should use stubs.

To summarise the differences;

  • Stubs
    • Provide canned answers to calls made during tests
    • Don’t respond to anything else
  • Mocks
    • Setup expectations - behaviour that will be verified as part of the test

Links to articles that discuss same: