How to implement Unit of Work that works with EF and NHibernate
I was working on a Unit of Work implementation that works both in Entity Framework 4.1 and NHibernate. Find below the skeleton of my implementation details
IUnitOfWork definition
public interface IUnitOfWork
{
IRepository<LogInfo> LogInfos { get; }
IRepository<AppInfo> AppInfos { get; }
void Commit();
void Rollback();
}
IRepository definition
public interface IRepository<T> where T : class, IEntity
{
IQueryable<T> FindAll();
IQueryable<T> FindWhere(Expression<Func<T, bool>> predicate);
T FindById(int id);
void Add(T newEntity);
void Remove(T entity);
}
Implementation of UoW in NHibernate
public class NHibernateUnitOfWork : IUnitOfWork, IDisposable
{
public ISession Session { get; private set; }
public NHibernateUnitOfWork(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
Session = _sessionFactory.OpenSession();
_transaction = Session.BeginTransaction();
}
public IRepository<LogInfo> LogInfos
{
get
{
if (_logInfo == null)
{
_logInfo = new NHibernateRepository<LogInfo>(Session);
}
return _logInfo;
}
}
public void Commit()
{
if (_transaction.IsActive)
_transaction.Commit();
}
}
Unit of Work in Entity Framework 4.1
public class SqlUnitOfWork : IUnitOfWork
{
private readonly ObjectContext _context;
public SqlUnitOfWork()
{
_context = new ObjectContext(connectionString);
_context.ContextOptions.LazyLoadingEnabled = true;
}
private SqlRepository<LogInfo> _logInfo = null;
public IRepository<LogInfo> LogInfos
{
get
{
if (_logInfo == null)
{
_logInfo = new SqlRepository<LogInfo>(_context);
}
return _logInfo;
}
}
public void Commit()
{
_context.SaveChanges();
}
}
Repository using NHibernate
public class NHibernateRepository<T> : IRepository<T> where T : class, IEntity
{
protected ISession Session;
public NHibernateRepository(ISession session)
{
Session = session;
}
public IQueryable<T> FindAll()
{
return Session.Query<T>();
}
public IQueryable<T> FindWhere(Expression<Func<T, bool>> predicate)
{
return Session.Query<T>().Where<T>(predicate);
}
public T FindById(int id)
{
return Session.Get<T>(id);
}
public void Add(T newEntity)
{
Session.Save(newEntity);
}
public void Remove(T entity)
{
Session.Delete(entity);
}
}
Repository using Entity Framework
public class SqlRepository<T> : IRepository<T> where T : class, IEntity
{
protected ObjectSet<T> ObjectSet;
public SqlRepository(ObjectContext context)
{
ObjectSet = context.CreateObjectSet<T>();
}
public IQueryable<T> FindAll()
{
return ObjectSet;
}
public IQueryable<T> FindWhere(Expression<Func<T, bool>> predicate)
{
return ObjectSet.Where(predicate);
}
public T FindById(int id)
{
return ObjectSet.Single(i => i.Id == id);
}
public void Add(T newEntity)
开发者_运维百科 {
ObjectSet.AddObject(newEntity);
}
public void Remove(T entity)
{
ObjectSet.DeleteObject(entity);
}
}
With this implementation I could get most of the features like saving, deleting, transaction working on both EF and NH. But when I start writing complex LINQ queries against Repositories NH fails most of the time. Some features like OrderBy and ToList throws errors when Repository is returning NhQueryable.
In the following code is called from ASP.NET MVC controller to which I'm injecting instance of IUnitOfWork using StructureMap. When NHibernateUnitOfWork is injected Where condition does not get applied where as it works as expected when SqlUnitOfWork is injected.
var query = from a in _unitOfWork.AppInfos.FindAll()
join l in _unitOfWork.LogInfos.FindAll()
on a.Id equals l.ApplicationId
where l.Level == "ERROR" || l.Level == "FATAL"
group l by new { a.Id, a.ApplicationName } into g
select new LogInfoSummaryViewModel()
{
ApplicationId = g.Key.Id,
ApplicationName = g.Key.ApplicationName,
ErrorCount = g.Where(i => i.Level == "ERROR").Count(),
FatalCount = g.Where(i => i.Level == "FATAL").Count()
};
return query.AsEnumerable();
As a side not building solution supporting different provides on top of the linq is way to disaster. Linq and IQueryable
are leaky abstractions - each Linq provider can have its own "features" and limitations. Moreover EF itselfs adds some logic via custom extension methods for IQueryable
(like Include
or AsNoTracking
in EFv4.1). These methods internally converts IQueryable
to ORM specific classes.
If you want to have universal solution you must abandon Linq and add third pattern to form the abstraction. In addition to Repository and Unit of Work patterns you need custom Specification pattern. Generally you will reimplement NHibernate's Criteria API.
From an IoC point of view and a desire for elegance your way is the way to go. However, all I read about NHibernate's linq provider is that it is still "beta-ish", because it is so damn hard to write Linq providers in the first place. So it might well be that you're just running into a bug here. Currently I would be very reluctant to write production code with Linq2Nhibernate. The new QueryOver feature is much more powerful. But of course, sadly, QueryOver doesn't fit seamlessly into your architecture, because you would have to use NHibernate syntax all the way. Complex Linq queries outside your repo would be useless because they would never get translated to SQL.
I'm afraid this effectively is the kiss of death to the elegance of your design, because, to start with, it would be useless to let a repository return an IQueryable<T>
. But returning IEnumerable<T>
would cripple your EF implementation. So, what is boils down to, I think that for querying both implementations are too different to fit behind one neat generic interface.
Here is a very useful post on QueryOver and Linq.
BTW: this is a very interesting question and design. I wish I could give more than one vote!
In addition to technical difficulties with QueryOver mentioned by Ladislav there may be a design issue. You would not have this problem if you approach it from Domain Driven Design perspective where Repository interface is based on Ubiquitous Language and does not expose things like IQueryable
which is a pure data access concept. This answer has information and links that you may find interesting.
精彩评论