Up to now, I did not care about NHibernate’s Sets and Bags, as my queries would target the details tables directly, and filter to the parent’s PK.
Now I tried to extend my Classes and ClassMaps generation code for Mapping.By.Code to Bags, and started out with something like this:
public partial class Foo { public virtual int Id { get; protected set; } public virtual ICollection<Bar> Bars { get; set; } } public partial class Bar { public virtual long Id { get; protected set; } public virtual Foo Foo { get; set; } }
The mapping for Bar is not affected by the new Bars property:
public partial class Bar_Map: ClassMapping<Bar> { public Bar_Map() { Table("Bar"); Id(x => x.Id, map => ...); ManyToOne(x => x.Foo, map => ...); } }
The master table gets a new collection property, depending on which NH mapping you use (SO):
Bag(x => x.Bars, bag => { bag.Table("Bar"); bag.Inverse(true); bag.Lazy(CollectionLazy.Lazy); bag.Cascade(Cascade.DeleteOrphans); bag.Key(k => { k.Column(col => col.Name("FooId")); // this is the SQL column name }); }, map => map.OneToMany());
So I try this code, retrieve a Foo, and count its Bars:
var foo = session.Get<Foo>(fooId); Console.WriteLine(foo.Id); Console.WriteLine(foo.Bars.Count());
Surprisingly, NH selects ALL Bar records into the collection, and counts the elements in the collection. That’s not what I expected.
On the NHibernate Pitfalls blog I found the hint to change the Lazy() setting to Lazy(CollectionLazy.Extra). And indeed, only a SELECT COUNT(*) was executed.
Somehow I was expecting the collection properties to be an alias for SELECT WHERE statements, so I tried things like foo.Bars.FirstOrDefault(), foo.Bars[0] (for IList) or foo.Bars.Take(1), but each of them always first populated the Bars collection in .Net, and only then retrieved the requested object from the collection, rather than issuing a separate SELECT.
For a complete list of documented surprises, see the NHibernate Pitfalls Index.
Glad it was useful! š