So after reading quite a few posts recently about folks dumping their hbm.xml files for fluent nhibernate, I decided to check it out for myself – and in the process formed a couple general thoughts about some other things.
First, a quick look at Fluent NHibernate. I was initially pretty excited about the prospects of using a C#-based interface for configuring my mappings – my presumption was that by declaring the maps in C# as opposed to XML, I would gain some additional compile-time type checking that I wasn’t currently getting with hbm (at least on one side of the map – I was not expecting for the library to magically go off to my database and validate it against the map (although now that I’m thinking about it, this functionality would make a nice build target or generic unit test)). As it turns out, I do get some additional type checking – that’s kind of hard to avoid when the map is specified using generics and lambda expressions for returning the mapped members. Unfortunately, at the end of the day, I still ended up with a MappingException that had absolutely 0 additional context. After beating my head against the wall for about half an hour, I concluded 2 things: 1) that I had a headache, and 2) that perhaps the mapping classes needed to be public. I went back and looked at the project sample, and yup, sure enough – I made the classes public and the sample ran just fine. I came away feeling validated in that the sample worked, but with somewhat of a bad taste in my mouth from having had to take a trial and error approach to figuring out the problem (even though, yea – I should have read looked more closely at the sample code).
I also found the session factory creation interface to be a bit less comfortable than the equivalent XML configuration settings. Maybe this is just the grumpy old guy in me talking here, but the following just didn’t feel all that natural.
private static ISessionFactory CreateSessionFactory() {
var factory =
Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2005.ConnectionString(
d => d
.Database("Demos.FluentNHibernate.FirstProject")
.Server("localhost")
.TrustedConnection())
.Raw("generate_statistics", "true"))
.Mappings(
m => m.FluentMappings.AddFromAssemblyOf<Program>())
.BuildSessionFactory();
return factory;
}
While I was following along with the sample, everything was fine, but when I had to go off-course a bit to wire up my application for profiling, there was no very obvious choice, so it was off to Stack Overflow for the solution.
At any rate, my general feeling about using the fluent interface approach for NHibernate mapping specifications is currently that while interesting, the simple change in style is not enough for me to switch over from hbms – and I’m not seeing enough additional gain in functionality or productivity. That said, I want to immediately make 1 exception for the auto-mapping functionality – that’s just pretty stinkin’ cool. And while I’m currently a little uncomfortable with simply applying it blindly, I’m sure that after a few weeks of using it in conjunction with NHibernate Profiler, I’ll be feeling pretty good about life without mapping files.
This brings me to my more general thought for the evening. As I was going through the fluent sample, the bi-directional association piece of it really jumped out at me as smelly. Specifically, I’m thinking about the following:
public class Store
{
public Store() {
Products = new List<Product>();
Staff = new List<Employee>();
}
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual IList<Employee> Staff { get; set; }
public virtual void AddProduct(Product product) {
product.StoresStockedIn.Add(this);
Products.Add(product);
}
public virtual void AddEmployee(Employee employee) {
employee.Store = this;
Staff.Add(employee);
}
}
public class Product
{
public Product() {
StoresStockedIn = new List<Store>();
}
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
public virtual IList<Store> StoresStockedIn { get; private set; }
}
along with the mapping classes:
public class StoreMap : ClassMap<Store>
{
public StoreMap() {
Id(s => s.Id);
Map(s => s.Name);
HasMany(s => s.Staff).Inverse();
HasManyToMany(s => s.Products).Cascade.All().WithTableName(
"StoreProduct");
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap() {
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Price);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Inverse()
.WithTableName("StoreProduct");
}
}
Specifically, I want to focus on the inverse statements and a couple ramifications I see from how this code is put together. First, the domain model is not really 100% persistent-ignorant. Ok, sure, it is PI in terms of mixing persistence code with domain logic. However, your model code must be structured in such a way that the behaviors drive people towards interacting with the model in a way that best suits the persistence requirements. This example model dealt with the persistence concerns of managing products and employees through the Store class by adding helper methods to it for managing the lifetimes of the Product and Employee instances. However (and secondly), the StoresStockedIn memeber of the Product class is a public property of type IList, which means that it has members for manipulating it’s members. This actually pushes the lack of semantic PI out of the domain model itself, and would require users of model to know (in this case) that in order to declare that a certain product is sold at a certain store, they need to add the Product instance to the Store via the helper method, rather than adding the Store instance to the Product’s StoresStockedIn collection. It’s not intuitively obvious, and to me that just feel’s…well…smelly.
I’m going to pull out the section I had written on lazy loading for right now and wrap on the bidirectional associations. Do you folks see a lot of these in your daily lives and how do you typically go about managing them? In a non-fluent world, I use the helper methods just like in the example, but then map the association to a private collection member and expose it via IEnumerable. Is this pretty straightforward to do with Fluent NHibernate?