3

I've read Unit of Work pattern and tons of MVC/EF Unit of Work examples but what I'm trying to do is encapsulate multiple unit-of-work into a large set. Maybe this pattern already exists somewhere, but I'm unaware of it if it does.

To keep my EF DbContexts clean, I've broken them down into different domains, however sometimes these domains overlap. I also need to do non-db type of Unit-Of-Work (like saving a file to the HD, and on rollback delete). I don't know if there is a different pattern for this or not, but I'd want to do something like:

// very rough idea
public class UnitOfWork
{
  public UnitOfWork(Func<bool> work, Action commit)
  {
    _work = work;
    _commit = commit;
  }
  public Func<bool> _work;
  public Action _commit;
  public bool HasExecuted { get; set; }
  public bool IsSuccessful { get; set; }
  public Exception Exception { get; set; }
  public bool Work()
  {
    try
    {
      HasExecuted = true;
      IsSuccessful = _work();
    }
    catch(Exception ex)
    { 
      Exception = ex;
    }
    return IsSuccessful;
  }
  public 
}

// very rough idea
public class TransactionWork
{
  private List<UnitOfWork> _uofs = new List<UnitOfWork>();
  public void Add(UnitOfWork uof)
  {
    _oufs.Add(uof);
  }
  public bool Commit()
  {
    var result = true;
    var exceptions = new List<Exception>();
    foreach(var uof in _uofs)
    {
      result = result && uof.Work();
      if (uof.Exception != null)
      {
        exceptions.Add(uof.Exception);
      }
    }

    if (result)
    {
      foreach(var uof in _uofs)
      {
        uof.Commit();
      }
    }
    else if (exceptions.Count > 0)
    {
      throw new AggregateException(exceptions);
    }
    return result;
  }
}

// Transaction would be a custom object I create to encapsulate
// multiple UoW.
var transactionWork = New TransactionWork();

transactionWork.Add(uof1);
transactionWork.Add(uof2);

var result = transactionWork.Commit();

If any of the uof's Commit's failed, all of them would be rolled back.

I'm curious if there is a pattern like this, or any types of improvements that I haven't thought of etc etc..

  • If I understand your problem correctly, you have some transactional database work that you wish to roll back if a "save file to disk" operation fails, correct? – Robert Harvey Feb 22 '16 at 22:12
  • Or the other way around. Say the save to the disk succeeds, but for whatever reason I am not able to create a record in the database, then the saved file would need to be rolled back (deleted). – Erik Philips Feb 22 '16 at 22:47
2

A fairly simple extension of the Unit of Work pattern would be do create an interface (call it ITransactionItem, perhaps) that contains commit and rollback methods. You could then make each of your unit of work objects implement that, along with any object that performs related functions, like your file operation. A new, top level unit of work would then hold a list of these and commit/rollback as appropriate. This seems similar to where you're going with the code posted.

Things to consider, however, are that if a failure occurs during the commit stage, any transactions that are already committed cannot be rolled back. If you have multiple units of work that cannot share transactions (e.g. because they do not operate with the same database) you'll need to figure out the potential consequences of that. If the consequences are unacceptable, you'll need to switch to a two-phase commit protocol.

  • At the moment, I cannot foresee the need for a two-phase commit. Do you know of any examples that are similar to this? I can't really believe I'm the first person to ever do this. – Erik Philips Feb 22 '16 at 22:48
  • 2
    @ErikPhilips: Actually, that "Two-Phase Commit" looks exactly like the software design pattern you are searching for. Might I suggest that finding a well-known software pattern and trying to shoehorn it into your problem might not be the best possible approach here? Why not simply analyze the problem and code up a solution yourself? For example: try saving the file first, and if that fails, no need to continue. If it succeeds, use a routine database transaction for the rest of the operation. Sometimes a cigar is just a cigar. – Robert Harvey Feb 22 '16 at 23:13
  • @RobertHarvey I completely agree. My question did include if other patterns exist which did something like this, that I might be unaware of. If not, I won't be using the term UnitOfWork in my project (because it's really not). As for your example, my problem is much more complicated then that, but it still boils down to a series of items that need to all complete or all commit, it's only similar to a UoW. – Erik Philips Feb 22 '16 at 23:57

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.