Quantcast
Channel: DarksideCookie - EntityFramework
Viewing all articles
Browse latest Browse all 4

Unit of Work and Entity Framework

0
0

The Unit of Work pattern is a really useful pattern that I really haven’t given a lot of thought to for some reason. Generally it just seems to need very little attention. I guess frameworks around us shield us from it… But I guess it the more you learn, the more you think of, and the more time you spend working on things that you previously never invested much thought in.

Anyhow, recently, working with Entity Framework (yes…it is another EF post…) I realized that this pattern is really key to a lot of things, and it isn’t always that easy to implement. So I decided to Google it and borrow heavily from some smarter person than me who had already built a nice solution for it. Unfortunately, all the information I found regarding UoW and EF was overly simplified, and generally based around abstracting away EF in a single repository.

The problem is the “single repository” thing. What happens when you need to have several repositories, or some parts of a system co-operating in the same UoW…well…no one seemed to care. It might be my situation that is screwed up, but in my case I needed this. I’m not sure that I like the way the system I am working on is built, but I wasn’t responsible for setting up those parts of the system…

The bottom line being that all the solutions including a single repo, with an internal DbContext is good and all, but a bit naïve…and even though my demo solution will also contain a single repo, it should work equally well in multiple… (I think, and hope…)

The problem is that since EF uses the DbContext as the definition of a UoW, any part of the system needing to work with that UoW needs to have a reference to a DbContext. This looks and feels wrong to me, and will very likely come back and bite you over and over again if you try. At least that is what I believe…

So, how do you share an instance of the DbContext, while at the same time abstracting it away to make sure that you don’t rely to heavily on EF? Well…it isn’t that easy…but I had to have a go at it. And the solution may or may not work. I haven’t had loads of time to test it (very little to be honest), so there might be flaws in it. But if you find flaws in it, please tell me…

In this simple demo, I will, as mentioned before, use a single repo, but that doesn’t matter. As the repo is not responsible for handling the DbContext, adding as many as you want shouldn’t make a difference, and they will still be able to be a part of the same UoW…

The first thing I need is obviously an EF model, so I created a really simple one

image

As you can see, it has two entities, User and Address, and a relation that defines that a User can have zero or more Addresses…

Together with the model, I have defined a repository called IUsers that can be used to get hold of User objects…

publicinterface IUsers
{
User WithId(int id);
}

Ok, nothing complicated so far. Just standard stuff, and in my case, I would normally just carry on and create an EntityFrameWorkUsers repository, which would be an Entity Framework based implementation of the IUsers interface, and it would get the DbContext injected in some way. However, I want something a little more flexible and abstract…(read complicated…)

To get away from the DbContext being the definition of a UoW, I have decided that I want another, more abstract definition of a unit of work. I named it ISession (I know nHibernate uses ISession as well, but that ISession is connected to nHibernate….and this session has NOTHING to do with with the specific ORM).

The ISession interface is ridiculously simple. It only implements what you REALLY need, which is a Commit() method.

publicinterface ISession : IDisposable
{
void Commit();
}

It also implements IDisposable to make sure that we can properly dispose of everything when we are done with it…

Ok, now that I have a way to define a unit of work, I need a way to get my hands on a session…which I do through an ISessionManager, which once again is the bare minimum needed…

publicinterface ISessionManager
{
ISession GetSession();
}

So the idea is that a client that needs to do some work can just do something like this

using (var s = sessionManager.GetSession())
{
// Do work
s.Commit();
}

The only thing to remember is that as soon as you dispose the session, you will dispose your DbContext and thus cause lazy loading to stop working. So if you want to keep that working, remember not to dispose the session… But I will get back to this… Oh, yeah…I also want an implicit session to be created if one doesn’t exist.

Ok, now I have a way to create a session/UoW, but I still have not added anything that has to do with EF…and I won’t at the moment… Instead, I will add some more abstractions (imagine that…me abstracting even more things…). However, these abstractions are a little different than the previous ones. The ISession and ISessionManager are abstractions that should be available to the “client”/consumer of the repos etc. The new ones are closer to the data and shouldn’t be used by the client/consumer. So placing them in another assembly means that the client doesn’t even need to know about them is a nice solution. In my demo solution, I placed them in the EF project, which isn’t great, but I didn’t feel like adding yet another project to this simple solution… Adding them to another project would “detach” them nicely from EF completely…

These new interfaces are IDataModelSession, IDataModelSessionManager, IDataModel and IDataModelFactory. They are all pretty simple so let’s have a quick look at them.

Let’s start with IDataModelSession, which is an extended ISession that exposes an extra property of type IDataModel…

publicinterface IDataModelSession : ISession
{
IDataModel Model { get; }
}

…and the IDataModelSessionManager is an extended ISessionManager with an extra method

publicinterface IDataModelSessionManager : ISessionManager
{
IDataModelSession GetDataModelSession();
}

The idea is that we create an implementation of IDataModelSession and IDataModelSessionManager and expose that to the data end of the system, but expose the same implementation as a generic ISession and ISessionManager to the client…

The last two interfaces, IDataModel and IDataModelFactory, are responsible for abstracting away the DbContext. The IDataModel looks like this in my case (this will vary based on the EF model)

publicinterface IDataModel : IDisposable
{
int SaveChanges();

IDbSet<User> Users { get; }
}

And as you can see, if you have some experience using EF, it resembles the DbContext a whole lot. That way, I can just use a partial class to extend the generated DbContext and make sure it implements this interface. The partial class file only needs to add the interface, as well as one property per entity type. As I expose it as an IDbSet<T> instead of DbSet<T>, the partial will have to add a property that converts the value… Like this

publicpartialclass MyModelContainer : IDataModel
{
public IDbSet<User> Users { get { return UserSet; } }
}

The final interface, the IDataModelFactory, is just responsible for creating IDataModel objects (big surprise…)

publicinterface IDataModelFactory
{
IDataModel CreateDataModel();
}

Ok, that was all the interfaces, time to implement them… Well, to some of them, the IDataModel is already done (mostly by EF)… And the IDataModelFactory is almost too easy, it is just a one-liner that creates the data model, which in my case is called MyModelContainer and created for me by my EF model…

publicclass DataModelFactory : IDataModelFactory
{
public IDataModel CreateDataModel()
{
returnnew MyModelContainer();
}
}

I actually create two implementations for the IDataModelSession. One that is a “root” session, and one that is a “sub session”. The “root” session is the one that contains the DbContext and handles the IDispose implementation and stuff, which is the one that is created when a brand new session is initiated. The sub session is much simpler. It is returned when a session is already running and someone requests a new session. That way, the second user will get a session object, but disposing or committing it will not actually do anything. Instead the “root” session will be responsible for that…

Let’s start with the DataModelSession object, which is very simple. It takes an IDataModel as a constructor parameter, and exposes it through the IDataModel property. It implements the Commit() method by first verifying that the session hasn’t already been committed, and if it hasn’t, calling SaveChanges() on the IDataModel.

The dispose implementation is a stock standard Dispose implementation, except for one thing. The DataModelSession class also has an event called Disposed, which is raised when the session is disposed. Why? Well, you will see that soon…

publicclass DataModelSession : IDataModelSession
{
privatebool _commited;
privatebool _disposed;
private IDataModel _dataModel;

public DataModelSession(IDataModel dataModel)
{
_dataModel = dataModel;
}

publicvoid Commit()
{
if (_commited)
thrownew SessionCommitedException();

_dataModel.SaveChanges();
_commited = true;
}

#region IDisposable
publicvoid Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protectedvirtualvoid Dispose(bool disposing)
{
if (disposing && !_disposed)
{
_dataModel.Dispose(); // Will cause lazy-loading to fail... Make sure it is only called if no more lazy-loading is to be done
_disposed = true;
OnDisposed();
}
_dataModel = null;
}
~DataModelSession()
{
Dispose(false);
}
#endregion

public IDataModel Model
{
get
{
if (_disposed)
thrownew ObjectDisposedException("DataModelSession");

return _dataModel;
}
}

protectedvirtualvoid OnDisposed()
{
var d = Disposed;
if (d != null)
d(this, new EventArgs());
}
internalevent EventHandler Disposed;
}

Ok…so now that I have a “root” session, let’s look at the “sub session” object… The DataModelSubSession implementation is real simple. It takes an IDataModelSession as a parameter to the constructor and stores it internally. It uses that instance to implement the Model property, but other than that, it does nothing…

publicclass DataModelSubSession : IDataModelSession
{
privatereadonly IDataModelSession _session;

public DataModelSubSession(IDataModelSession session)
{
_session = session;
}

publicvoid Commit()
{
}

publicvoid Dispose()
{
}

public IDataModel Model { get { return _session.Model; } }
}

Ok, so how are these used? Well, it isn’t really that complicated. The IDataModelSessionManager implementation is pretty straight forward. It takes an IDataModelFactory as a constructor parameter and stores it for later use. The implementation of the GetSession() always creates a new session, which is stored as the current session before it is returned. I also makes sure to hook up a handler for the Disposed event before returning it. In the handler, it unhooks the handler to release the object, and if the disposed session is the current session, it sets the current session to null.

In the GetDataModelSession() method, I check to see if there is a current session. If there is, I return a new DataModelSubSession, passing in a reference to the current session, otherwise I return a new DataModelSession. I do NOT store the new DataModelSession as the current session, as I want this to keep implicit sessions running to make sure lazy loading works. This is not perfect, but the session will be disposed when all objects referencing it goes out of scope, so it should be ok I hope. But in a high load scenario, I would probably suggest handling sessions explicitly…

publicclass SessionManager : IDataModelSessionManager
{
privatereadonly IDataModelFactory _modelFactory;
private DataModelSession _currentSession;

public SessionManager(IDataModelFactory modelFactory)
{
_modelFactory = modelFactory;
}

public ISession GetSession()
{
_currentSession = (DataModelSession)GetDataModelSession();
_currentSession.Disposed += CurrentSessionOnDisposed;
return _currentSession;
}

public IDataModelSession GetDataModelSession()
{
if (_currentSession == null)
{
returnnew DataModelSession(_modelFactory.CreateDataModel());
}
returnnew DataModelSubSession(_currentSession);
}

privatevoid CurrentSessionOnDisposed(object sender, EventArgs eventArgs)
{
var session = (DataModelSession)sender;
session.Disposed -= CurrentSessionOnDisposed;
if (session == _currentSession)
_currentSession = null;
}
}

That is actually all there is too the whole session management stuff…

So how do I go about building a repo on top of this? Well, that is actually quite simple… In this case, I have only built a simple one called IUser, as mentioned before…All repos take an IDataModelSessionManager as a constructor parameter, and then just uses that when getting hold of a session. And since it uses GetDataModelSession(), if there is a session running, it will get a “sub” session, otherwise it will get it’s own session. So if a session is already running, calling Commit() or Dispose() won’t actually do anything, while if there is no running sessions, those calls would do their thing… So the IUser repository looks like this

publicclass Users : IUsers
{
privatereadonly IDataModelSessionManager _sessionManager;

public Users(IDataModelSessionManager sessionManager)
{
_sessionManager = sessionManager;
}

public User WithId(int id)
{
var session = _sessionManager.GetDataModelSession();
return session.Model.Users.FirstOrDefault(x => x.Id == id);
}
}

As you can see, it has very little, or no relation with EF. The only thing that is EF specific is the IDbSet<Users> on the IDataModel. I guess it would be possible to abstract those away as well as IDbSet<T> implements IList<T>, but I will just ignore that for now. The only time it would really useful to do that, is if we switch ORM, which seems a bit unlikely. And in that case, we would have to refactor the IDataModel interface to during that switch, which probably wouldn’t be too hard. But as I said, if you REALLY wanted to, you could probably get around it by exposing something more generic, like IList<T>.

Ok, that’s it! My way to solve UoW with EF. To demo it, there is obviously downloadable code below. This time however, I have decided to use NUnit and unit tests to show off the functionality instead of building some useless application. I hope that makes sense. I have also used a bit of NuGet packages. These are not included in the Zip, instead there is a NuGet targets thingy that should restore the packages automatically for you…

Downloadable code: DarksideCookie.EF.UOWDemo.zip (276.98 kb)


Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles





Latest Images