views:

103

answers:

3

Is it possible to adopt the use of an O/RM like NHibernate or Entity Framework, and abstract it in such a way that it could be replaced if a situation is encountered that the O/RM cannot handle.

It seems tempting to create a service with chunky service methods inside of which, a session is created, the session is used to get / upsert entities and is then used to save all dirty objects.

I would have considered the repository pattern so that the service operation asks the repository for entities, and the O/RM session is embedded in the repository. But what do you do about saving related entities, and does an Update(T entity) method flush changes imediately. It seems to simple and generally unsatisfactory.

What I am leaning toward now is a single O/RM wrapper class that exposes an interface with generic methods like "StartSession", "EndSession", "AbandonSession", "GetById(object id)" etc.

At least this would allow the OR/M to be faked in testing which is another big concern of mine.

I guess I'm saying that I don't want to closely intertwine business logic, and O/RM data access code, because switching to another O/RM could cause most of that code to be replaced.

What do people do in the real world?

A: 

I handle C/U/D transactions to the database with the unit of work pattern. I don't go much further than that in abstracting, because I don't want to abstract the abstraction of the abstraction. Linq is a good abstraction for the querying part, a generic repository is a good abstract for the querying part. These are the abstractions I use in almost any project with a relational database:

interface IRepository<T> : IQueryable<T>
{
   void Add(T entity);
   void Remove(T entity);
   T Get(Guid id);
}

interface IUnitOfWork : IDisposable
{
   void RollBack();
   void Commit();
}

// I don't do this every time, the generic repository is enough for almost everything.
[Example]
interface IOrderRepository : IRepository<Order>
{
   IList<Order> GetOrdersForUser(User user);
}

// This interface is only used in the repository implementation
interface INHiberanteUnitOfWork : IUnitOfWork
{
  ISession Session { get; }
}

Sometimes, I use the generic repository, sometimes I use a more specific generic repository with specification pattern support, and sometimes I create a separate repository for a specific entity type. You can search stack overflow on NHibernate + Unit of work to find more information about implementing these patterns.

I don't abstract the mapping, because every orm has it's own features in mapping, and I think the mapping is already an abstraction. I use fluentnhibernate for mapping.

Paco
Would an instance of INHibernateUnitOfWork be shared between repositories?Would the code using the repositories have responsibility for calling IUnitOfWork.Commit or Rollback, so that work done in any repository is ultimately saved or cancelled?How do you decide what repositories are needed? One per major entity? I've heard the expression "aggregate root object".
IanT8
I wish I could submit a code sample of a service operation using more than one repository.
IanT8
A unit of work is used per user screen in a desktop app, or per request in a web app, so one unit of work is used by all the repositories involved in showing a screen to the user, and the commit or rollback method in the unit of work is called at the end of the users request, so that all data is submitted at once. The repositories do not call commit or rollback. They just register the changes in the unit of work, by using the session. I use one repository per aggregate root.
Paco
A: 

One option is to define your domain model as POCOs, and then use BoostMap to proxy between your custom types and the underlying OR/M. This lets you swap out the underlying OR/M if needed, and also mock up an iQueryable source for unit testing that doesn't rely on the database :-)

Joel Martinez
The only non-poco things with NHibernate are:1. Virtual methods when you want to use lazy loading.2. A (public or nonpublic) default constructor.1. If you want to use lazy loading, you won't have any benefit from any tool at runtime, because virtual will still be required to proxy the pocos for lazy loading. This can only be avoided with a code generation tool, or a post compiler process.2. I don't see why it's worth the effort to use another tool just to avoid writing a few (stupid) default constructors.How does BoostMap give you advantage over NHiberante?
Paco
no no, you would still use nhibernate (or EF, or Linq2Sql), but BoostMap lets you put a proxy in front of your OR/M model so that none of your code has to explicitly reference the underlying ORM features in order to express queries.
Joel Martinez
I mean: What benefit will boostmap deliver, it will not remove the dependency on a orm, it just adds another dependency.
Paco
Of course it wouldn't remove the dependency on the ORM, if it did, it would be little more than just another ORM. In this case, it allows your code to remove any explicit references to the ORM code (at least for queries) ... which, as the original poster said, lets you swap it out for another if such is required. For create/update/delete scenarios, another abstraction could easily work ... for example, even the repository pattern you suggested in your post
Joel Martinez
A: 

My strong opinion would be to do some tests on smaller projects and choose a toolset to use up front rather then put off the decision until later by building an abstract wrapper that you'll be able to change "someday". In my experience that someday never comes and the complexity you introduce is not worth it.

I have attempted to build a generic wrapper for nHibernate in the past and essentially met with nothing but pain because of it. ORM Tools are really a wrapper and toolset themselves. When you try to create a wrapper of a wrapper, you end up needing to develop (and subsequently maintain) relatively complex tools and mechanisms to accomplish what the thing you are wrapping does natively.

And, in my own experience, the wrapper I spent a LARGE amount of time building ended up only exposing a fraction of the abilities of the underlying ORM tools. And then I NEVER ended up using the wrapper with anything but nHibernate... so I could have saved myself massive amounts of time and effort by just using vanilla nHibernate itself.

I would recommend looking closely at what your requirements are and instead of spending the time abstracting out a swiss army knife solution, do the research, pick a toolset first and run with it. Yes your code will be tightly coupled to a third party tool, but the code will be simpler, you will get there faster and, likely, the results will be better.

Hope that helps,

Max Schilling

Max Schilling