Monthly Archives: December 2012
The Value of Features of ORM’s

At Redacted Associates, we’ve been having a discussion about whether we should use the Generic Repository pattern on top of NHibernate. We have a simple IRepository interface as follows:

For my part, I like don’t like to spend a lot of time worrying about the way my ORM interacts with the database. I prefer to spend my design-energy around how to architect the application such that interacting with the database is a minor implementation detail, almost an afterthought even.

At least one of my co-workers disagrees, and has given a really good argument for a specific case when using direct features in NHibernate saved some work. This discussion has spurred me to ask myself “what are the most important features of an ORM?” and “at what level of abstraction should we work with an ORM?” There’s no way to answer these questions without identifying your purpose in using the ORM to begin with. With that in mind, I decided to categorize the features we all look for in an ORM and compare them to our Generic Repository implementation.

ORM features basically fall into one of 3 categories:

  1. Queryability

    • Linq Provider

      In .NET, Linq remains the most discoverable way to query an ORM. NHibernate has the QueryOver api, but I find it to be hopelessly unreadable anything but the simplest query.

    • Specification pattern

      The easiest specification pattern in .NET relies on Linq. It’s a very nice way to allow api clients to construct their own queries without concerning themselves with database schema details. In an SOA architecture, it provides a flat contract to support complex query results. It minimizes the number of service methods because you don’t have to write GetCustomersByNameAndCityAndProduct.

    • Fully mapped relationships between objects.

      I depend on having a fully expressed query model to use for linq queries. Getting the right data to work with often involves a number of database relationships and it’s impossible to predict when some new object or property will be needed to satisfy a query. It’s easiest to ensure that the model fully expresses the database and that all fields and relationships are present in the model. The model should fully and accurately express the database schema.

    • Id navigation properties.

      Id navigation properties as a companion to the object relationship properties are really handy. They can reduce query-syntax clutter quite a bit. Employee.ManagerId is cleaner than Employee.Manager.Id. Some ORM’s will pull back the whole Manager to get the Id. I hate that.

    • Full support for all relationship types (one-to-one, one-to-many, many-to-many).

      These relationships are standard in relational db’s. Any Object-Relational Mapper should support them.

    • Lazy Loading
  2. Behavior

    • Cascade mappings.

      This is not personally a value to me, but I recognize that in some cases it’s useful.

    • Trigger-behavior.

      This sort of behavior is useful when you want the ORM to handle things like audit fields, soft deletes, or log table entries.

    • Sql-Efficiencies.

      Sometimes pulling back large datasets and mapping them to in-memory objects can be very expensive. If performance is a concern, it’s nice to be able to have the ORM optimize the operation. NHibernate’s “Merge” operation is a good example of this.

  3. Testability

    • In-memory testability
    • Mockable/Stubbable

I composed the following table listing the features we are seeking from an ORM and how each tool fares against our needs.

NHibernate
Entity Framework
Generic Repository
Micro ORMs
Linq Provider
Not Fully Implemented Fully Implemented Depends on ORM No
Specification Pattern
Easy to implement against partially implemented Linq provider. Hard otherwise. Easy to implement. Depends on ORM No
Can Fully Map Relationships
Yes Yes Depends on ORM No
Id Navigation Properties
Not without extreme (and not very useful) hacks Yes Depends on ORM Yes
Full support for relationship types
One-to-one doesn’t work well. Results in N+1 queries on one side of the relationship * Haven’t tested this. Depends on ORM Sort of
Lazy Loading
Yes Yes Depends on ORM No
Cascade Mappings
Yes Yes Depends on ORM No
Trigger Behavior
Yes Yes Depends on ORM No
Sql Efficiences
Yes Yes Depends on ORM No
In-memory testability
Yes, provided you use SqlLite. Yes, provided you use SqlCompact edition—or you can interface your DbContext. Yes No
Mockable-Stubbable
Mostly. Some of the methods you use on ISession are in fact extension methods. .Query is an extension method which is problematic since that’s an obvious one I’d like to stub. Mostly. Methods such as .Include() are extension methods with no in-memory counterpart. If I really need to use .Include() I’ll write an integration test instead. Yes No
Notes
  • ISession.Query is an extension method. Not testable.
  • ISession.QueryOver is a difficult api for complex queries, especially those involving groups.
  • Automappings make map-by-configuration easier.
  • No (current) support for convention-based configuration.
  • No Merge operation.
  • Generic repository does not give you direct access to methods like ISession.Merge.
  • You have two ways of dealing with this:
    1. Use ISession (or DbContext) directly.
    2. You can inherit or adapt the IRepository interface to provide the other methods.
Fantastic for quick and dirty CRUD on single tables. Not so great otherwise.

 

Takeaways

My advice is to use Generic Repository except when you need to get close to the metal of your ORM for some fine-grained control over your data access.