But anywho, it didn't take long for me to run into very many problems with NHibernate. It's not really NHibernate's fault, but more of maybe I'm trying to use the wrong tool for the job.
I'm using DDD for my project, and there's a clear separation between the client and the domain. I got lazy and I didn't make any DTOs for my entities, and just used the [DataContract] and [DataMember] to have WCF automatically generated "DTO"s for me (and in the options turn OFF reusing assemblies).
(Disclaimer: I am *not* by any means knowledgeable about NHibernate, but maybe, just maybe what I say here might point other people in the right direction)
OK, all is good. I can store my entities in the database, I can read them back. So I fire up my client and read some users, and it blows up. NHibernate by default lazy loads everything. Here's what I originally had in my UsersRepository
public IEnumerable<User> GetAll() { using (var session = _factory.OpenSession()) return session.CreateCriteria<User>().List<User>(); }
It looks pretty innocent. But it blows up. Why? If Users has a collection of other entities (which mine did), they are not loaded with the above code. They are loaded on-demand, i.e. lazy loaded. So when my WCF service finally returns the objects and serializes them, it pukes and throws an exception because the session was long closed already.
Easy solution was to Not.LazyLoad() all my collections. Now here's what I'm thinking I might not be using the right tool for the job because I am purposely disabling one of the key features of NHibernate. By default, caching is always enabled, and I couldn't find anywhere how to globally turn it off. You must go class by class.
OK, so on to my next problem. I soon ran into issues with entities not being unique. I would have code like this in my UserService:
public void AddProduct(string username, Guid productId) { var user = _usersRepository.GetUser(username); var product = _productRepository.GetProduct(productId); user.Products.Add(product); _usersRepository.Store(user); }
Again, this puked. The problem here was after my GetUser call, inside my repository, like my first example, I had a using(session), which closed when the method ended. Shortly after, I am trying to update the same user, but with a *different* session. NHibernate doesn't like this, an NHibernate.NonUniqueObjectException is thrown, and I had a new problem to deal with.
It became clear I had to actively manage the sessions somehow, and the best place would be to have them in my services, which typically each method had a "unit of work" placed on them.
So the goal I wanted to accomplish was to initiate a new session at the beginning of a service method, call a repository multiple times, close the session, and then exit the method.
So how can we achieve that?
I thought of a couple things, and the first thing I did actually was to use AOP style and use Autofac with Castle.DynamicProxy. I created an interceptor for my service, and before invocation I opened a session, then manually called a property setter for a CurrentSession, and after invocation close the session.
It did the job, but had some problems:
a) It did a little too much black magic for me. All methods got intercepted.
b) I still had to manually set the CurrentSession property of my repositories. Sooner or later I'm sure to run in the threading problems.
After I got that half working I scrapped it and tried to come up with something better. This is what I came up with:
public delegate IUsersRepository UsersRepositoryFactory(ISession session); public class UserService : IUserService { public UserService(ISessionFactory factory, UsersRepositoryFactory usersFactory) { ... } } public class UsersRepository : IUsersRepository { public UsersRepository(ISession session) { ... } }
Now, when I call a method on my UserService, I do this:
public void AddProduct(string username, Guid productId) { using (var session = _factory.OpenSession()) { var userRepo = _userRepoFactory(session); var productRepo = _productFactory(session); ... } }
And, of course, the Autofac code:
b.Register<UsersRepository>().As<IUsersRepository>().FactoryScoped(); b.RegisterGeneratedFactory<UsersRepositoryFactory>();
More details on the delegate factory here. But basically, with Autofac I have told it to inject delegates into my UserService, and those delegates can create new instances of my user repository by passing in the current session.
You'll also notice I had a products repository as well. With these delegate factories I can create up multiple repositories on the fly with the same session (and all the good NHibernate stuff that comes with that).
Sure, there's a whole bunch of using(var session) boilerplate now, but it's explicit, clear, and allows for a lot of fine-grained control.
EDIT: I have since dropped this approach and I'm currently injecting ISessions into my constructors by using Autofac's container-scope (something that took me a while to understand).
No comments:
Post a Comment