DRY with Lambda Expression (errors management)
In my current project, I have made a miracle with Lambda Expression. Have you ever implemented something like this:
public abstract class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
private readonly ILog _logger;
protected GenericRepository()
: this(IoC.GetInstance<IUnitOfWork>(), IoC.GetInstance<ILog>()
)
{
}
protected GenericRepository(IUnitOfWork unitOfWork, ILog logger)
{
UnitOfWork = unitOfWork;
_logger = logger;
}
public IUnitOfWork UnitOfWork { get; set; }
public IEnumerable<TEntity> All()
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
try
{
return UnitOfWork.Session.QueryOver<TEntity>().List();
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
public int Add(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
try
{
return (int)UnitOfWork.Session.Save(entity);
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
public void Remove(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
try
{
UnitOfWork.Session.Delete(entity);
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
public void Update(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
try
{
UnitOfWork.Session.Update(entity);
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
public IEnumerable<TEntity> GetBy(Expression<Func<TEntity, bool>> condition)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(condition != null, "Expression is null");
try
{
return UnitOfWork.Session.QueryOver<TEntity>()
.Where(condition)
.List();
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
}
Yeah. Many try...catch... you use for error management. And I think it is very stupid, and violate the DRY (Don't repeat yourself) principle. I give a example, one day, I will change the error management, so I must to go from the begin this class to end this class for modify it. Once again, it is very stupid and take a lot of time to manage. So I always think one solution for resolve it some weeks ago. And now in new project, I apply one technical that use the lambda expression for inverting code control.
It is very simply. I think after I present it at here, anyone also know about it.
I just make a helper class as here:
public static class CatchExceptionHelper
{
/// <summary>
/// Tries the catch action.
/// for example, void Foo() is a action
/// </summary>
/// <param name="action">The action.</param>
/// <param name="logger">The logger.</param>
public static void TryCatchAction(Action action, ILog logger)
{
try
{
action();
}
catch (Exception ex)
{
logger.Error(ex.Message);
throw;
}
}
/// <summary>
/// Tries the catch function.
/// for example, int Foo() is a function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="function">The function.</param>
/// <param name="logger">The logger.</param>
/// <returns></returns>
public static T TryCatchFunction<T>(Func<T> function, ILog logger)
{
try
{
return function();
}
catch (Exception ex)
{
logger.Error(ex.Message);
throw;
}
}
}
After that just do that as here:
public abstract class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
private readonly ILog _logger;
protected GenericRepository()
: this(IoC.GetInstance<IUnitOfWork>(), IoC.GetInstance<ILog>()
)
{
}
protected GenericRepository(IUnitOfWork unitOfWork, ILog logger)
{
UnitOfWork = unitOfWork;
_logger = logger;
}
public IUnitOfWork UnitOfWork { get; set; }
public IEnumerable<TEntity> All()
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
return CatchExceptionHelper.TryCatchFunction(() =>
{
return UnitOfWork.Session.QueryOver<TEntity>().List();
}, _logger);
}
public int Add(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
return CatchExceptionHelper.TryCatchFunction(() =>
{
return (int)UnitOfWork.Session.Save(entity);
}, _logger);
}
public void Remove(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
CatchExceptionHelper.TryCatchAction(() => UnitOfWork.Session.Delete(entity), _logger);
}
public void Update(TEntity entity)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(entity != null, "Entity is null");
CatchExceptionHelper.TryCatchAction(() => UnitOfWork.Session.Update(entity), _logger);
}
public IEnumerable<TEntity> GetBy(Expression<Func<TEntity, bool>> condition)
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
CodeContract.Contract.Assert(condition != null, "Expression is null");
return CatchExceptionHelper.TryCatchFunction(() =>
{
return UnitOfWork.Session.QueryOver<TEntity>()
.Where(condition)
.List();
}, _logger);
}
}
Now we shall be easy to change error management any time that not touch to this class, just modify helper class. But as you see we still need to check the Code Contract at the begin of each method. I also think it is also not good. So I will re factoring something like that:
public abstract class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
private readonly ILog _logger;
protected GenericRepository()
: this(IoC.GetInstance<IUnitOfWork>(), IoC.GetInstance<ILog>()
)
{
}
protected GenericRepository(IUnitOfWork unitOfWork, ILog logger)
{
UnitOfWork = unitOfWork;
_logger = logger;
}
public IUnitOfWork UnitOfWork { get; set; }
public IEnumerable<TEntity> All()
{
return CheckContractViolated(() =>
{
return CatchExceptionHelper.TryCatchFunction(() =>
{
return UnitOfWork.Session.QueryOver<TEntity>().List();
}, _logger);
});
}
public int Add(TEntity entity)
{
CodeContract.Contract.Assert(entity != null, "Entity is null");
return CheckContractViolated(() =>
{
return CatchExceptionHelper.TryCatchFunction(() =>
{
return (int)UnitOfWork.Session.Save(entity);
}, _logger);
});
}
public void Remove(TEntity entity)
{
CodeContract.Contract.Assert(entity != null, "Entity is null");
CheckContractViolated(() =>
{
CatchExceptionHelper.TryCatchAction(() => UnitOfWork.Session.Delete(entity), _logger);
});
}
public void Update(TEntity entity)
{
CodeContract.Contract.Assert(entity != null, "Entity is null");
CheckContractViolated(() =>
{
CatchExceptionHelper.TryCatchAction(() => UnitOfWork.Session.Update(entity), _logger);
});
}
public IEnumerable<TEntity> GetBy(Expression<Func<TEntity, bool>> condition)
{
CodeContract.Contract.Assert(condition != null, "Expression is null");
return CheckContractViolated(() =>
{
return CatchExceptionHelper.TryCatchFunction(() =>
{
return UnitOfWork.Session.QueryOver<TEntity>()
.Where(condition)
.List();
}, _logger);
});
}
private void CheckContractViolated(Action action)
{
CheckContractViolated();
action();
}
private TReturn CheckContractViolated<TReturn>(Func<TReturn> function)
{
CheckContractViolated();
return function();
}
private void CheckContractViolated()
{
CodeContract.Contract.Assert(UnitOfWork != null, "Unit of work is null");
CodeContract.Contract.Assert(UnitOfWork.Session != null, "Session is null");
}
}
As a code above, I add three functions with named is CheckContractViolated and just delegate the responsibility to it.If anyone don't like delegated the LambdaExpression for manage the contract, you only need the third CheckContractViolated method at end of this class. But I like Lambda Expression :D. Very simple, but with me it is very cool! Happy coding!