0

I read a lots of content about the usage of Entity Framework/NHibernate (or basically any other modern ORM) with the repository/UnitOfWork patterns. Apparently the community is divided. Some would say the repository pattern is almost mandatory, some others would say that it's a waste of time... Well, I come up with my "own" design and I just wanted to share it with you in order to get some feedback...

In the past, my company decided to develop and use it's own ORM. It's now a total disaster. Performance, stability (and basically everything else) are terrible. We want to switch to another ORM and we want to be keep the ability to switch from an ORM to another. Indeed, we are now using Sharepoint 2010. It means 3.5 and thus NHibernate 3.4 and Entity Framework 4. We plan to migrate to SharePoint 2013 ASAP in order to be able to rely on .net 4.5/EF 6.1/... So we will have to switch to another ORM pretty soon.

To do so, I have developed a set of classes implementing the "IDatabaseContext" interface.

public interface IDatabaseContext : IDisposable
{
    IQueryable<TEntity> AsQueryable<TEntity>()
        where TEntity : EntityBase;
    IList<TEntity> AsList<TEntity>()
        where TEntity : EntityBase;

    IList<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate)
        where TEntity : EntityBase;
    long Count<TEntity>()
        where TEntity : EntityBase;

    void Add<TEntity>(TEntity entity)
        where TEntity : EntityBase;
    void Delete<TEntity>(TEntity entity)
        where TEntity : EntityBase;
    void Update<TEntity>(TEntity entity)
        where TEntity : EntityBase;
}

For example, for the prototype I have decided to use NHibernate:

public class NHibernateDbContext : IDatabaseContext
{
    private ISession _session = null;

    public NHibernateDbContext(ISessionFactory factory)
    {
        if (factory == null)
            throw new ArgumentNullException("factory");
        _session = factory.OpenSession();
    }

    public IQueryable<TEntity> AsQueryable<TEntity>()
        where TEntity : EntityBase
    {
        return _session.Query<TEntity>();
    }

    public IList<TEntity> AsList<TEntity>()
        where TEntity : EntityBase
    {
        return _session.QueryOver<TEntity>()
                       .List<TEntity>();
    }

    public IList<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate)
        where TEntity : EntityBase
    {
        ...
    }

    public long Count<TEntity>() 
        where TEntity : EntityBase
    {
        return _session.QueryOver<TEntity>()
                       .RowCountInt64();
    }

    public void Add<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        if (entity == null)
            throw new ArgumentNullException("entity");
        UseTransaction(() => _session.Save(entity));
    }

    public void Delete<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        ...
    }

    public void Update<TEntity>(TEntity entity)
        where TEntity : EntityBase
    {
        ...
    }

    private void UseTransaction(Action action)
    {
        using (var transaction = _session.BeginTransaction())
        {
            try
            {
                action();
                transaction.Commit();
            }
            catch
            {
                transaction.Rollback();
                throw;
            }
        }
    }

    public void Dispose()
    {
        if (_session != null)
            _session.Dispose();
    }
}

Eventually, my service layer (each entity are associated with a service) relies on this interface so I don't introduce a dependency over the ORM technology.

public class CountryService<Country> : IService<Country>
    where Country : EntityBase
{
    private IDatabaseContext _context;

    public GenericService(IDatabaseContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        _context = context;
    }

    public IList<Country> GetAll()
    {
        return _context.AsList<Country>();
    }

    public IList<Country> Find(Expression<Func<Country, bool>> predicate)
    {
        return _context.Find(predicate);
    }

    ...
}

Eventually, to call a method from the service layer, you just need two lines of code:

    var service = new CountryService(new NHibernateDbContext(...)));
    or 
    var service = new CountryService(new TestDbContext(...)));
    ...

I find this architecture quite simple and very convenient to use. I didn't find (yet) any drawback/flaws/errors.

So what do you think? Did I miss something big? Is there something I can improve?

Thank you for all your feedback...

Regards, Sebastien

  • The manage of the session is not common, what kind of application will use this base? – Najera Apr 25 '15 at 13:51
  • 2
    perhaps better suited to http://programmers.stackexchange.com/questions/ask – phil soady Apr 25 '15 at 14:16
  • SharePoint WebParts will mostly rely on this layer. In addition, few applications may also use this layer (for sync purposes between different data sources). What do you mean by "manage of the session"? –  Apr 27 '15 at 03:27

2 Answers2

0

Personally i think your approach is solid. But equally Im surprised you see this approach as very different to the repository / Unit of Work Pattern. Your IService layer is directly comparable to a basic Unit of Work layer combined with a repository layer implemented via an interface. The repository layer should implement an Interface and be injected or discovered by a core layer to avoid dependency on the underlying ORM.

The actual implementation of your Repository and Unit of Work layers would be specific to the underlying ORM. But you could replace a RepositoryEF class with RespositoryNH or vice versa. If done properly and used with dependency injection, the Core application never knows what the ORM is.

The problem is with SOME peoples Repository Patterns, they leak the underlying orm by allowing code that accesses ORM directly or leaks the ORMs structures.

eg if an IREPOSITORY exposed DBSet or Context from Entity Framework, then the whole app can be locked to EF.

eg Unit Of Work Interface

 public interface ILuw  {
    IRepositoryBase<TPoco> GetRepository<TPoco>() where TPoco : BaseObject, new();
    void Commit(OperationResult operationResult=null, bool silent=false);
}

and a IRepositoryBase interface

 public interface IRepositoryBase<TPoco> : IRepositoryCheck<TPoco>  where TPoco : BaseObject,new() {
    void ShortDump();
    object OriginalPropertyValue(TPoco poco, string propertyName);   
    IList<ObjectPair> GetChanges(object poco, string singlePropName=null);
    IQueryable<TPoco> AllQ();
    bool Any(Expression<Func<TPoco, bool>> predicate);
    int Count();
    IQueryable<TPoco> GetListQ(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetList(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetListOfIds(List<string>ids );
    IOrderedQueryable<TPoco> GetSortedList<TSortKey>(Expression<Func<TPoco, bool>> predicate,
                                                     Expression<Func<TPoco, TSortKey>> sortBy, bool descending);


    IQueryable<TPoco> GetSortedPageList<TSortKey>(Expression<Func<TPoco, bool>> predicate,
                                                  Expression<Func<TPoco, TSortKey>> sortByPropertyName, 
                                                  bool descending,
                                                  int skipRecords, 
                                                  int takeRecords);

    TPoco Find(params object[] keyValues);
    TPoco Find(string id); // single key in string format, must eb converted to underlying type first. 
    int DeleteWhere(Expression<Func<TPoco, bool>> predicate);
    bool Delete(params object[] keyValues);
    TPoco Get(Expression<Func<TPoco, bool>> predicate);
    TPoco GetLocalThenDb(Expression<Func<TPoco, bool>> predicate);
    IList<TPoco> GetListLocalThenDb(Expression<Func<TPoco, bool>> predicate);
    TU GetProjection<TU>(Expression<Func<TPoco, bool>> predicate, Expression<Func<TPoco, TU>> columns);
    /// <summary>
    /// To use the projection  enter an anonymous type like  s => new { s.Id , s.UserName});
    /// </summary>
    IList<TU> GetProjectionList<TU>(Expression<Func<TPoco, bool>> predicate, Expression<Func<TPoco, TU>> columns);


    bool Add(object poco,bool withCheck=true);
    bool Remove(object poco);
    bool Change(object poco, bool withCheck=true);
    bool AddOrUpdate(TPoco poco, bool withCheck = true);
  }
phil soady
  • 11,043
  • 5
  • 50
  • 95
  • Thank you for all your inputs. However I still have a small question. I don't understand why using a UoW pattern is relevant. Indeed, most of the time, you will end up doing things like update > save; add > save; delete > save. So here, what are the benefits of using the UoW pattern? –  Apr 27 '15 at 02:46
  • The Unit of Work pattern allows you to combine updates to multiple repositories in one commit. A repository pattern often only addresses 1 table from a DB. So update1 table1, update2 from table2 are committed together usuing unit of work x. The fascade IUoW allows the UOW tool to be mocked or replaced – phil soady Apr 27 '15 at 07:43
0

Your approach is sound, but there are few considerations

  1. AsList<TEntity>() method is a recipe for abuse, many new developers might user this method to get list of data and will probably do the filtering in memory
  2. How transactions are handled.

Personally I'd go with the repository pattern phil soady suggested, but I would not use that much of methods in the IRepository interface.

public interface IRepository<T> where T:IEntity
{
    T Single(long id);

    T Save(T entity);

    void Delete(T entity);

    IQueryable<T> FilterBy(Expression<Func<T, bool>> expression);
}

And when the type specific query is required it's handled in it's own repository, like

public interface IContactRepository : IRepository<Contact>
{
    IList<Contact> GetForUser(int userId);
}

And handling of transactions, I would go for Using the unit of work per request pattern where you don't have to manually handle transaction each and every time database is updated. A global ActionFilter would be enough to achieve this.

Community
  • 1
  • 1
Low Flying Pelican
  • 5,974
  • 1
  • 32
  • 43
  • Hi thank you for your answer. ActionFilter seems to be a very clean and easy solution to handle transaction. However, I'm sure i can use ActionFilters in projects targeting .net 3.5? –  Apr 27 '15 at 01:35
  • Action filters are not a new concept, and available since the very first versions of MVC, if you cannot use them for some reason you could always use a httpmodule – Low Flying Pelican Apr 27 '15 at 10:34