Home > Software Development > Castle ActiveRecord and Eager Fetching

Castle ActiveRecord and Eager Fetching

In the beginning of 2006 we began using NHibernate at InterWorks. The learning curve to NHibernate is steep and I quickly searched for any tools or frameworks that could mitigate the learning curve by simplifying the mapping and querying process. I found ActiveRecord at that time, but it was still a work in progress and I didn’t feel it was safe to use in production projects.

Thus began our journey to create our own framework around NHibernate. We created something very similar to ActiveRecord, but we did not hide the entire NHibernate implementation as ActiveRecord does. Instead we created a base object class (creatively named DataObjectBase) and wrote a MyGeneration template to generate our mapped objects from a database. We have this refined to the point where we can design our database and easily generate dataobjects. That template generates several convenience static methods on the dataobjects: GetById, GetByProperty, GetByCriteria, and others.

Being the kind of person that hates to do something manually twice, I’m constantly looking to automate processes. Lately the issue I’ve been thinking about is how we can more easily track and maintain database changes across version of projects. One of our guys is a former RoR evangelist (he still likes Ruby, but realizes that Rails is not a silver bullet ?) and brought up the Rails migration feature. We began to search for a migration framework to use with our NHibernate framework. During the course of that search, I found ActiveWriter for Castle ActiveRecord. A nice tool to do exactly what we need… if only we used ActiveRecord!

This prompted me to take another look at ActiveRecord. We won’t be able to move to it during this coming year, but in 2010 I’d like to move us back to a framework that has community support, be that ActiveRecord or ADO.NET Entity Framework (would have to be next release of VS.NET, current version is too limited). This prompted me to take a second look at ActiveRecord and examine if there were features missing that would keep us from using it and if so were they surmountable issues that we could implement ourselves?

One of the common arguments I have heard against ActiveRecord is that it oversimplified the mapping of objects to tables and did not allow enough flexibility to do complex mapping. That issue doesn’t bother me because we only use our NHibernate framework on new databases and have a standard database design approach that should easily work with ActiveRecord. As long as it can support joined subclasses (which it does), we are good on that front.

One issue that was tricky for us to get right with our framework was lazy loading. We tried setting lazy loading in the mapping, but quickly realized this was problematic. The data you want to lazy load is usually dependent on the context of where you are using the object and since you have no context at the point of the mapping, you simply have to make a decision based on the most likely case. Luckily, NHibernate allows you set a relationship to eager load either through HQL or the Criteria queries. You can do this by calling ICriteria.SetFetchMode(association,FetchMode.Eager) on the SubCriteria object that represents the join you want to eager load.

This would be problematic with our framework because we were trying to minimize how often the application developer had to use the Criteria object, preferring them to use our convenience methods. To resolve this we added another parameter to our convenience methods called associationsToLoad. This string would be parsed by our framework and traverse the Criteria object tree and set the specificed associations to be eagerly loaded. Here’s the code for the method that does the heavy lifting:

  1.  
  2. /// Adds all the specified associations to the ICriteria. This will cause these associations
  3. /// to be loaded in one database query with the top level object.
  4. ///
  5. /// The associationsToLoad string should be in the format "association1,association2".
  6. /// The associations themselves can have multiple depth, e.g. "Company.Clients.State".
  7. /// Associations can duplicate safely, e.g. "Company.State,Company.Orders".
  8. /// All associations are set to load using a left outer join.
  9. public static void AddAssociations(ICriteria criteria, string associationToLoad)
  10. {
  11. if (String.IsNullOrEmpty(associationToLoad))
  12. return;
  13.  
  14. Dictionary<string, ICriteria> subcriteriaMap = GetSubcriteria(criteria);
  15. List<string> aliases = new List<string>();
  16.  
  17. string[] associationStrings = associationToLoad.Split(',');
  18. foreach (string associationString in associationStrings)
  19. {
  20. string[] associations = associationString.Trim().Split('.');
  21.  
  22. ICriteria parentCriteria = null;
  23. ICriteria subcriteria = null;
  24.  
  25. string association = "";
  26. string absolutePath = "";
  27.  
  28. for (int i = 0; i < associations.Length; i++)
  29. {
  30. association = associations[i];
  31.  
  32. if (i == 0)
  33. {
  34. parentCriteria = criteria;
  35. absolutePath = association;
  36. }
  37. else
  38. {
  39. parentCriteria = subcriteria;
  40. absolutePath = String.Format("{0}.{1}", absolutePath, association);
  41. }
  42.  
  43. if (subcriteriaMap.ContainsKey(absolutePath))
  44. subcriteria = subcriteriaMap[absolutePath];
  45. else
  46. {
  47. subcriteria = parentCriteria.CreateCriteria(association, GetAlias(association, aliases), JoinType.LeftOuterJoin);
  48. subcriteriaMap.Add(absolutePath, subcriteria);
  49. }
  50.  
  51. parentCriteria.SetFetchMode(association, FetchMode.Eager);
  52. }
  53. }
  54. criteria.SetResultTransformer(CriteriaUtil.DistinctRootEntity);
  55. }
  56.  
  57.  

This works great. It allows you to quickly optimize your page by passing in the string without having to create a Criteria query and manually set FetchMode.Eager on each association.

Unfortunately, ActiveRecord does not currently support anything like this. You have to jump down to HQL to preload associations outside of the mapping. This is just way to onerous for me. Call me lazy, but one of the major reasons to use an ORM was so you don’t have to write SQL, and to me writing HQL isn’t much better. However, I think that this would be relatively easy for us to implement on top of the ActiveRecord framework. If not, we can write it into the framework itself and submit to the community for approval.

After further review, I didn’t see any other issues that would keep me from using ActiveRecord at this time. It has an active community and appears to be a very healthy project. However, this move will be a large move for us and I will abstain my decision until I see the new version of the ADO.NET Entity Framework in VS.NET 2010.

Categories: Software Development Tags:
  1. January 6th, 2010 at 07:36 | #1

    public static ICriteria GetSubcriteria(ICriteria criteria, string associationPath)
    {
    if (criteria is CriteriaImpl.Subcriteria)
    throw new NHibernateFrameworkException("You cannot call GetSubcriteria on a criteria that IS a subcriteria. Call it on the root level criteria.");

    if (!(criteria is CriteriaImpl))
    throw new NHibernateFrameworkException("Unknown type of ICriteria");

    CriteriaImpl criteriaImpl = (CriteriaImpl)criteria;

    IEnumerable subs = criteriaImpl.IterateSubcriteria();
    foreach (CriteriaImpl.Subcriteria subcriteria in subs)
    {
    if (subcriteria.Path == associationPath)
    return subcriteria;
    }
    return null;
    }

  2. Manuel
    January 6th, 2010 at 03:55 | #2

    Very nice!

    How do you implemented, the
    GetSubcriteria(criteria)?

  3. Danyal
    October 31st, 2009 at 07:36 | #3

    Nice function! (Yoink)

  4. scolemann
    January 29th, 2009 at 20:51 | #4

    Yes, you can absolutely use part of it, I’m glad you liked it :)

  5. January 27th, 2009 at 19:40 | #5

    Wow! Thank you very much!
    I always wanted to write in my blog something like that. Can I take part of your post to my blog?
    Of course, I will add backlink?

    Regards, Your Reader

  1. No trackbacks yet.