Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
like image 541
Raj Avatar asked Sep 14 '11 05:09

Raj


People also ask

What is EF core work unit?

The unit of work class coordinates the work of multiple repositories by creating a single database context class shared by all of them. If you wanted to be able to perform automated unit testing, you'd create and use interfaces for these classes in the same way you did for the Student repository.

Why do we use Unit of Work pattern?

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.

Do we need Repository pattern with entity Framework Core?

No, the repository/unit-of-work pattern (shortened to Rep/UoW) isn't useful with EF Core. EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful.

Is Unit of Work necessary?

The only reason to still have a unit of work is if you: want to include non-EF-datasources in an atomic data operation. want to use a unit of work in your domain without relying on an EF dependency on that layer.


2 Answers

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.

like image 170
Ladislav Mrnka Avatar answered Nov 03 '22 19:11

Ladislav Mrnka


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!

like image 37
Gert Arnold Avatar answered Nov 03 '22 19:11

Gert Arnold