15
\$\begingroup\$

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.

Here's the DataContext class that inherits 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);
    }
}
\$\endgroup\$
  • \$\begingroup\$ "The concrete implementation of the unit of work uses entity framework under the hood (DbContext)" - and yet you declare a DataContext - it's not very clear what's going on here, what version of EF are you using? Oh wait, have you called your DbContext implementation "DataContext"? \$\endgroup\$ – Mathieu Guindon Sep 20 '14 at 23:35
  • \$\begingroup\$ @Mat'sMug Yes, the DbContext implementation is the DataContext class. I updated the content of the question to include the DataContext code snippet. In your opinion, would it be better to rename it to a less confusing name? Do you have any suggestions for a new name? \$\endgroup\$ – Guillermo Gomez Sep 20 '14 at 23:49
  • \$\begingroup\$ Maybe it's just me, but DataContext clashes with the Linq-to-SQL context class in my mind (System.Data.Linq.DataContext). \$\endgroup\$ – Mathieu Guindon Sep 20 '14 at 23:51
  • \$\begingroup\$ Oh, I see... I wasn't aware of the Linq-to-SQL name conflict, thanks for bringing that up. I will modify it. \$\endgroup\$ – Guillermo Gomez Sep 20 '14 at 23:55
7
\$\begingroup\$
public interface IUnitOfWork : IDisposable
{
    IRepository<T> Repository<T>() where T : class;
    void Commit();
    void Rollback();
}

The goal of a unit of work is to abstract the disposable stuff that's implementing it. By making your interface extend IDisposable, you've made a leaky abstraction - now anyone (including your mocking framework) that wants to implement this interface must also implement IDisposable, even though the only implementation that clearly needs it, is the one that's wrapping a DbContext.

I'm not sure I'm following here:

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;
    }

DataContext lives in the System.Data.Linq namespace - that's LINQ-to-SQL, not Entity Framework! I'm not all that familiar with LINQ-to-SQL, and I don't know how much it differs from an EF DbContext - I pretty much skipped L2S and went from ADO.NET to EF...

With EF, I'd consider my DbContext-derived class as my unit of work, and make it implement my IUnitOfWork interface - I'd just tweak yours a little:

public interface IUnitOfWork
{
    IDbSet<T> Repository<T>() where T : class;
    void Commit();
    void Rollback();
}

And then your DataContext class (bad, way too generic name IMO) needs only to expose IDbSet<T> - which is a repository.

Yes! Entity Framework implements Unit-of-Work + Repository patterns for you!

EF is an abstraction all by itself; wrapping it in yet another abstraction is overkill and introduces useless complexity; there's no need for a UnitOfWork class, and no need for a Repository<T> class.

Don't get me wrong - I love UoW+Repository. It's the best thing since sliced bread, I've implemented it over ADO.NET, and even over ADODB in VB6/VBA. But over Entity Framework, it's overkill.


#region is not needed, I'd remove all of them. It's good that you're not using it inside methods (that would mean your method is doing too many things), but this isn't useful in any way:

#region Private Constants
private const string PORTFOLIO_REQUEST_VALID_FILE_TYPES = "PortfolioRequestValidFileTypes";
#endregion

If you change your mind and make it a private static readonly string instead, then you have a comment/region to maintain as well. That said, I would make it a private static readonly string and move its value to config.

\$\endgroup\$
  • \$\begingroup\$ @ Mat's Mug - So what happens if you no longer what to use Entity Framework and have to now use another ORM for let's say an Oracle implementation. Shouldn't you have some sort of abstraction around the framework using something like the Repository pattern? \$\endgroup\$ – gcoleman0828 Sep 15 '15 at 15:12
  • 2
    \$\begingroup\$ @gcoleman0828 Repository Pattern isn't a silver bullet. Even if you abstract the abstraction, every single query will need to be rewritten anyway; that's why people don't swap ORM's for the heck of it. Wrapping them all into a general-purpose "repository" box, reduces every ORM framework to a mere data access tool, and defeats the purpose of using an ORM - what's there to gain from switching from EF to Oracle (is that an ORM or a RDBMS?), if at the end of the day you could just as well be using bare-metal (i.e. more performant) ADO.NET wrapped-up with the very same interfaces? \$\endgroup\$ – Mathieu Guindon Sep 15 '15 at 15:25
  • \$\begingroup\$ The point I'm making isn't the reason for switching. I am saying if you had to. I have had several instances where there were reasons above my pay grade that we needed to support both types of databases. I think you are assuming that the back end database type will never change For instance, as far as I know, Entity Framework does not support Oracle and you would now need to either do 'bare metal' or use another ORM like DevArt. If you wrap you ORM around this, it is one less place things need to be changed depending on the ORM, \$\endgroup\$ – gcoleman0828 Sep 15 '15 at 15:44
  • 1
    \$\begingroup\$ @gcoleman0828 you seem to be confusing Object-Relational Mappers and RDBMS. I've swapped mySQL for SQL Server with very little code changes and without a repository pattern - all one needs is an EF provider, and a quick google search for any RDBMS I could think of, including mySQL, Oracle, and even DB2, has existing EF providers. Swapping ORM would be more like swapping EF for NHibernate, which for the higher-ups is nothing but an implementation detail IMO. \$\endgroup\$ – Mathieu Guindon Sep 15 '15 at 16:13
3
\$\begingroup\$

Why not inject the unit of work into the repository? Then in your service layer call commit. Your controllers shouldn't know about the unit of work. They should just talk directly to the Service/Business Logic layer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.