I am designing a web application and a windows service and want to use the unit of work + repository layer in conjunction with a service layer, and I am having some trouble putting it all together so that the client apps control the transaction of data with the unit of work.
The unit of work has a collection of all repositories enrolled in the transaction along with commit and rollback operations
public interface IUnitOfWork : IDisposable
{
IRepository<T> Repository<T>() where T : class;
void Commit();
void Rollback();
}
The generic repository has operations that will be performed on the data layer for a particular model (table)
public interface IRepository<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, IList<ISortCriteria<T>> sortCriterias = null);
PaginatedList<T> GetPaged(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, PagingOptions<T> pagingOptions = null);
T Find(Expression<Func<T, bool>> filter, IList<Expression<Func<T, object>>> includedProperties = null);
void Add(T t);
void Remove(T t);
void Remove(Expression<Func<T, bool>> filter);
}
The concrete implementation of the unit of work uses entity framework under the hood (DbContext) to save the changes to the database, and a new instance of the DbContext class is created per unit of work
public class UnitOfWork : IUnitOfWork
{
private IDictionary<Type, object> _repositories;
private DataContext _dbContext;
private bool _disposed;
public UnitOfWork()
{
_repositories = new Dictionary<Type, object>();
_dbContext = new DataContext();
_disposed = false;
}
The repositories in the unit of work are created upon access if they don't exist in the current unit of work instance. The repository takes the DbContext as a constructor parameter so it can effectively work in the current unit of work
public class Repository<T> : IRepository<T> where T : class
{
private readonly DataContext _dbContext;
private readonly DbSet<T> _dbSet;
#region Ctor
public Repository(DataContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
#endregion
I also have a service classes that encapsulate business workflow logic and take their dependencies in the constructor.
public class PortfolioRequestService : IPortfolioRequestService
{
private IUnitOfWork _unitOfWork;
private IPortfolioRequestFileParser _fileParser;
private IConfigurationService _configurationService;
private IDocumentStorageService _documentStorageService;
#region Private Constants
private const string PORTFOLIO_REQUEST_VALID_FILE_TYPES = "PortfolioRequestValidFileTypes";
#endregion
#region Ctors
public PortfolioRequestService(IUnitOfWork unitOfWork, IPortfolioRequestFileParser fileParser, IConfigurationService configurationService, IDocumentStorageService documentStorageService)
{
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
}
if (fileParser == null)
{
throw new ArgumentNullException("fileParser");
}
if (configurationService == null)
{
throw new ArgumentNullException("configurationService");
}
if (documentStorageService == null)
{
throw new ArgumentNullException("configurationService");
}
_unitOfWork = unitOfWork;
_fileParser = fileParser;
_configurationService = configurationService;
_documentStorageService = documentStorageService;
}
#endregion
The web application is an ASP.NET MVC app, the controller gets its dependencies injected in the constructor as well. In this case the unit of work and service class are injected. The action performs an operation exposed by the service, such as creating a record in the repository and saving a file to a file server using a DocumentStorageService, and then the unit of work is committed in the controller action.
public class PortfolioRequestCollectionController : BaseController
{
IUnitOfWork _unitOfWork;
IPortfolioRequestService _portfolioRequestService;
IUserService _userService;
#region Ctors
public PortfolioRequestCollectionController(IUnitOfWork unitOfWork, IPortfolioRequestService portfolioRequestService, IUserService userService)
{
_unitOfWork = unitOfWork;
_portfolioRequestService = portfolioRequestService;
_userService = userService;
}
#endregion
[HttpPost]
[ValidateAntiForgeryToken]
[HasPermissionAttribute(PermissionId.ManagePortfolioRequest)]
public ActionResult Create(CreateViewModel viewModel)
{
if (ModelState.IsValid)
{
// validate file exists
if (viewModel.File != null && viewModel.File.ContentLength > 0)
{
// TODO: ggomez - also add to CreatePortfolioRequestCollection method
// see if file upload input control can be restricted to excel and csv
// add additional info below control
if (_portfolioRequestService.ValidatePortfolioRequestFileType(viewModel.File.FileName))
{
try
{
// create new PortfolioRequestCollection instance
_portfolioRequestService.CreatePortfolioRequestCollection(viewModel.File.FileName, viewModel.File.InputStream, viewModel.ReasonId, PortfolioRequestCollectionSourceId.InternalWebsiteUpload, viewModel.ReviewAllRequestsBeforeRelease, _userService.GetUserName());
_unitOfWork.Commit();
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
return View(viewModel);
}
return RedirectToAction("Index", null, null, "The portfolio construction request was successfully submitted!", null);
}
else
{
ModelState.AddModelError("File", "Only Excel and CSV formats are allowed");
}
}
else
{
ModelState.AddModelError("File", "A file with portfolio construction requests is required");
}
}
IEnumerable<PortfolioRequestCollectionReason> portfolioRequestCollectionReasons = _unitOfWork.Repository<PortfolioRequestCollectionReason>().Get();
viewModel.Init(portfolioRequestCollectionReasons);
return View(viewModel);
}
On the web application I am using Unity DI container to inject the same instance of the unit of work per http request to all callers, so the controller class gets a new instance and then the service class that uses the unit of work gets the same instance as the controller. This way the service adds some records to the repository which is enrolled in a unit of work and can be committed by the client code in the controller.
One question regarding the code and architecture described above. How can I get rid of the unit of work dependency at the service classes? Ideally I don't want the service class to have an instance of the unit of work because I don't want the service to commit the transaction, I just would like the service to have a reference to the repository it needs to work with, and let the controller (client code) commit the operation when it see fits.
On to the windows service application, I would like to be able to get a set of records with a single unit of work, say all records in pending status. Then I would like to loop through all those records and query the database to get each one individually and then check the status for each one during each loop because the status might have changed from the time I queried all to the time I want to operate on a single one. The problem I have right now is that my current architecture doesn't allow me to have multiple unit of works for the same instance of the service.
public class ProcessPortfolioRequestsJob : JobBase
{
IPortfolioRequestService _portfolioRequestService;
public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
{
_portfolioRequestService = portfolioRequestService;
}
The Job class above takes a service in the constructor as a dependency and again is resolved by Unity. The service instance that gets resolved and injected depends on a unit of work. I would like to perform two get operations on the service class but because I am operating under the same instance of unit of work, I can't achieve that.
For all of you gurus out there, do you have any suggestions on how I can re-architect my application, unit of work + repository + service classes to achieve the goals above?
I intended to use the unit of work + repository patterns to enable testability on my service classes, but I am open to other design patterns that will make my code maintainable and testable at the same time while keeping separation of concerns.
Update 1 Adding the DataContext class that inheris from EF's DbContext where I declared my EF DbSets and configurations.
public class DataContext : DbContext
{
public DataContext()
: base("name=ArchSample")
{
Database.SetInitializer<DataContext>(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
base.Configuration.ProxyCreationEnabled = false;
}
public DbSet<PortfolioRequestCollection> PortfolioRequestCollections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new PortfolioRequestCollectionConfiguration());
base.OnModelCreating(modelBuilder);
}
}
If your purpose for using Unit of Work (UoW) was for testability, you took the wrong path. Unit of work does nothing for testability. Its main purposes is to provide atomic transactions to disparate data sources, provide UoW functionality to a data layer that doesn't already provide it, or to wrap an existing UoW in a way that makes it more easily replaceable... something which you've nullified by using the generic repository (this tightly couples it to Entity Framework anyways).
I suggest you get rid of the Unit of Work completely. Entity Framework is already a UoW. Even Microsoft has changed their mind and no longer recommend UoW with EF.
So, if you get rid of UoW, then you can just use repositories to wrap your EF queries. I don't suggest using a generic repository, as this leaks your data layer implementation all over your code (something your UoW was already doing), but rather create Concrete repoTsitories (these can use generic repositories internally if you like, but the generic repository should not leak outside of your repository).
This means your service layer takes the specific concrete repository it needs. For instance, IPortfolioRepository
. Then you have a PortfolioRepository
class that inherits from IPortfolioRepository which takes your EF DbContext
as a parameter that gets injected by your Depndency Injection (DI) framework. If you configure your DI container to instance your EF context on a "PerRequest" basis, then you can pass the same instance to all your repositories. You can have a Commit
method on your repository that calls SavesChanges
, but it will save changes to all changes, not just to that repository.
As far as Testability goes, you have two choices. You can either mock the concrete repositories, or you can use the built-in mocking capabilities of EF6.