NHibernate - Mapping Custom Collections
Came upon this article on mapping custom collections that uses extension methods. I'm not a massive fan of extension methods to be honest and I prefer to use custom collection classes and I thought I'd show how I've seen them mapped in the past.
When I say that I like custom collections I mean classes like this:
public class FooCollection : ReadOnlyCollection<Foo>
I prefer a collection like this over a bog standard IList<Foo> because there is usually a lot of behaviour related to the collection, for example we may have custom rules relating to addition/removal and we'll probably want to ensure the collections contents are valid at all times. One solution would be to use encapsulate collection but I've found that it just results in my aggregate roots and key entities getting bogged down in lots of boring collection work so I prefer to farm out this work to custom collections. Plus if I go for the custom collection approach I can ensure they implement interfaces such as these:
public interface IReadOnlyCollection<TItem> : IReadOnlyCollection, ...
{
bool Contains(TItem toEvaluate);
IReadOnlyCollection<TItem> FindAll(Specification<TItem> specification);
...
}
This sort of interface is very useful because IEnumerable misses some important members and IList is too permissive of change, plus we want to be able to add all sorts of useful helper methods like the FindAll shown.
However I can't directly map my FooCollection, NHibernate is only going to be able to map something basic like an IList. The solution is to map the collection like this:
<component name="FooCollection" access="nosetter.camelcase-underscore">
<bag name="_innerList" cascade="all-delete-orphan" access="field" lazy="true">
<key column="ContaingClassesID" />
<one-to-many class="..." />
</bag>
</component>
This requires a little explaining. We're mapping our custom collection as a component, this seems odd until you look at the way we then map the _innertList within our custom collection. Essentially this _innerList is an IList<Foo> hidden within FooCollection (or more correctly one of its base classes). Since we're mapping an interface NHibernate is happy and the users of our custom collection don't know that we've had to put in this silly _innerList, plus maintaining/using this little internal collection is taken care of by a base class so its all painless. One slight smell is that we're mapping _innerList as a field, not perfect but its not going to keep me up at nights.
Anyway its not a perfect solution but it lets you map custom collections without sacrificing too much.
We came up with a solution like this and it is a useful shortcut, if you don't want to go with a full blown custom NH collection implementation.
ReplyDeleteOne niggle is that you need to include the name of the inner list field in the property path within your Criteria queries, e.g. .AddCriteria("OrderLines._innerList.Product.Id"... Naming the inner list something like "Items" minimises the mess.
@Dan
ReplyDeleteGood point, I'd forgotten about that aspect.
I was thinking about doing something like this, for similar reasons.
ReplyDeleteI'b interesting in seeing how your Specification works. Can you show more code for this? Do you hit the database, or is it for in-memory filtering, or both?
@Tobin
ReplyDeleteHey.
The specifications in the approach I'm describing were just for in-memory as this was pre-Linq but when I next try it I'm going to try Linq specifications and then you could use them in memory and/or could use them in the repositories.
Only problem I have with that approach right now is that Linq to NHib isn't fully enough implemented to support some of the things I need.
Colin, this article is exactly what I was looking for.
ReplyDeleteHowever i am finding it a tad hard to follow from your examples.
In particular:
" public interface IReadOnlyCollection< TItem > : IReadOnlyCollection, ..."
I do not see how this applies to the Custom collection implimenting
ReadOnlyCollection
Its late and i may just be being stupid.
Dave The Ninja
@Dave
ReplyDeleteSorry yeah, basically your FooCollection inherits either directly from IReadonlyCollection or from ReadOnlyCollection (a base class that takes care of 90+% of the nonsense needed to implement IEnumerable and so on).
Means I can expose the FooCollection directly, or expose IEnumerable or expose IReadonlyCollection.
In FooCollection you add any custom behavior e.g. disallowing adding Foos if there is another Foo of that type already in the collection.
Does that make sense?
Perfect sense now!
ReplyDeleteI thought you had them all inheriting off each other for a moment - and thats just crazy talk.
Going to impliment it now.
Will let you know.
Dave
Cool, definitely be interested to see what you think of the result.
ReplyDeleteExcellent post as always, but it leaves me a little confused about the role of custom collections vs repositories. Repositories emulate a custom collection, thereby abstracting persistence concerns. Assuming Order is an aggregate root, are you saying you might have an OrderRepository and an OrderCollection?
ReplyDeleteSince I use my blog to help me remember things...
ReplyDeleteAlso worth noting is that you want to enforce the rules on collections upfront.
If you don't then the contents of the collection can be invalid and when validating the owning aggregate you have to validate the entire collection which is inefficient and over-complicated.