41

I'm struggling to understand the relationship between the Repository and Unit of Work patterns despite this kind of question being asked so many times. Essentially I still don't understand which part would save/commit data changes - the repository or the unit of work?

Since every example I've seen relates to using these in conjunction with a database/OR mapper let's make a more interesting example - lets persist the data to the file system in data files; according to the patterns I should be able to do this because where the data goes is irrelevant.

So for a basic entity:

public class Account
{
    public int Id { get; set; }
    public string Name { get; set; }
}

I imagine the following interfaces would be used:

public interface IAccountRepository
{
     Account Get(int id);
     void Add(Account account);
     void Update(Account account);
     void Remove(Account account);
}

public interface IUnitOfWork
{
    void Save();
}

And I think in terms of usage it would look like this:

IUnitOfWork unitOfWork = // Create concrete implementation here
IAccountRepository repository = // Create concrete implementation here

// Add a new account
Account account = new Account() { Name = "Test" };
repository.Add(account);

// Commit changes
unitOfWork.Save();

Bearing in mind that all data will be persisted to files, where does the logic go to actually add/update/remove this data?

  1. Does it go in the repository via the Add(), Update() and Remove() methods? It sounds logical to me to have all the code which reads/writes files in one place, but then what is the point of the IUnitOfWork interface?
  2. Does it go in the IUnitOfWork implementation, which for this scenario would also be responsible for data change tracking too? To me this would suggest that the repository can read files while the unit of work has to write files but that the logic is now split into two places.
Peter Monks
  • 4,219
  • 2
  • 22
  • 38

5 Answers5

36

Repository can work without Unit Of Work, so it can also have Save method.

public interface IRepository<T>
{
     T Get(int id);
     void Add(T entity);
     void Update(T entity);
     void Remove(T entity);
     void Save();
}

Unit Of Work is used when you have multiple repositories (may have different data context). It keeps track of all changes in a transaction until you call Commit method to persist all changes to database(file in this case).

So, when you call Add/Update/Remove in the Repository, it only changes the status of the entity, mark it as Added, Removed or Dirty... When you call Commit, Unit Of Work will loop through repositories and perform actual persistence:

  • If repositories share the same data context, the Unit Of Work can work directly with the data context for higher performance(open and write file in this case).

  • If repositories have different data context(different databases or files), the Unit Of Work will call each repository's Save method in a same TransactionScope.

phnkha
  • 7,782
  • 2
  • 24
  • 31
  • I think this explanation is the clearest to me. Reading what others have said, this would suggest that if I will only ever have one data store (e.g. database) then I might do without Unit of Work and just use `IRepository.Save()` for simplicity. But if I do have multiple data stores then the Unit of Work would hold a reference to each repository and the `Commit()` would invoke each `IRepository.Save()` method, thereby letting each repository handle it's own logic. Would that sound correct? – Peter Monks Jan 11 '13 at 11:14
  • 1
    It still needs one relational database for that to work. Each repo may have its own transaction which is ok, but if at one point you're saving on the cloud and the application crashes, you'll end up with inconsistent state. The unit of works is just a concept after all, the problem is the actual implementation and that depends a lot on your setup. So, a great solution for EF would fail for NoSql, while a solid overall solution may be too complex for what the app needs. – MikeSW Jan 11 '13 at 11:34
8

I'm actually quite new to this but as nobody wiser has posted:

The code which CRUDs happens in the repositories as you would expect, but when Account.Add (for example) is called, all that happens is that an Account object is added to the list of things to be added later (the change is tracked).

When unitOfWork.Save() is called the repositories are allowed to look through their list of what has changed Or the UoW's list of what has changed (depending on how you choose to implement the pattern) and act appropriately - so in your case there might be a List<Account> NewItemsToAdd field that has been tracking what to add based on calls to .Add(). When the UoW says it's OK to save, the repository can actually persist the new items as files, and if successful clear the list of new items to add.

AFAIK the point of the UoW is to manage the Save across multiple repositories (which combined are the logical unit of work that we want to commit).

I really like your question. I've used Uow / Repository Pattern with Entity Framework and it shows how much EF actually does (how the context tracks the changes until SaveChanges is finally called). To implement this design pattern in your example you need to write quite a bit of code to manage the changes.

Neil Thompson
  • 6,356
  • 2
  • 30
  • 53
  • 1
    Thanks for your comment about liking the question. All I've ever seen regarding examples of these patterns is how to use it with something like EF or NHibernate and never any other data store - it doesn't have to be a database after all. Starting to see that Unit of Work is more about managing multiple data stores/repositories at once, thanks for your explanation – Peter Monks Jan 11 '13 at 10:09
  • Old post, but if anyone is interested, a custom data store with change tracking could be implemented with INotifyPropertyChanged and the help of the PropertyChanged.Fody library which automatically injects notification code to auto-properties on build. I've used that tool for a simlar task and it's really powerful. – Zoltán Tamási Jul 22 '14 at 20:41
4

Ehe, things are tricky. Imagine this scenario: one repo saves something in a db, other on the file system and the third something on the cloud. How do you commit that?

As a guideline, the UoW should commit things, however in the above scenario, Commit is just an illusion as you have 3 very different things to update. Enter eventual consistency, which means that all things will be consistent eventually (not in the same moment as you're used with a RDBMS).

That UoW is called a Saga in a message driven architecture. The point is every saga bit can be executed at different time. Saga completes only when all 3 repositories are updated.

You don't see this approach as often, because most of the time you'll work with a RDBMS, but nowadays NoSql is quite common so a classic transactional approach is very limited.

So, if you're sure you work ONLY with ONE rdbms, use a transaction with the UoW and pass teh associated connection to each repository. At the end, UoW will call commit.

If you know or expect you might have to work with more than one rdbms or a storage that doesn't support transactions, try to familiarize yourself with a message driven architecture and with the saga concept.

MikeSW
  • 16,140
  • 3
  • 39
  • 53
4

Using the file system can complicate things quite much if you want to do it on yourself.

Only write when the UoW is committed.

What you have to do is to let the repositories enqueue all IO operations in the UnitOfWork. Something like:

public class UserFileRepository : IUserRepository
{
    public UserFileRepository(IUnitOfWork unitOfWork)
    {
        _enquableUow = unitOfWork as IEnquableUnitOfWork;
        if (_enquableUow == null) throw new NotSupportedException("This repository only works with IEnquableUnitOfWork implementations.");

    }

    public void Add(User user)
    {
        _uow.Append(() => AppendToFile(user));
    }

    public void Uppate(User user)
    {
        _uow.Append(() => ReplaceInFile(user));
    }
}

By doing so you can get all changes written to the file(s) at the same time.

The reason that you don't need to do that with DB repositories is that the transaction support is built into the DB. Hence you can tell the DB to start a transaction directly and then just use it to fake a Unit Of Work.

Transaction support

Will be complex as you have to be able to roll back changes in the files and also prevent different threads/transactions from accessing the same files during simultaneous transactions.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
1

normally, repositories handle all reads, and unit-of-work handles all writes,but for sure you can handle all reads and writes by only using one of these two (but if only using repository pattern, it will be very tedious to maintain maybe 10 repositories,more worse,maybe result in inconsistent reads and writes be overwritten), advantage of mix using both is ease of tracing status change and ease of handling concurrency and consistent problems. for better understanding,you can refer links: Repository Pattern with Entity Framework 4.1 and Parent/Child Relationships and https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled

Community
  • 1
  • 1
LIU YUE
  • 1,593
  • 11
  • 19