Unit of Work
Unit of Work tracks all objects changed during a business transaction and commits inserts, updates, and deletes together in one database transaction. It ensures atomic, consistent writes and fewer round trips, with object-tracking complexity.
Unit of Work is a data-access pattern that maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. Rather than saving each modified object immediately, it records what has changed during a unit of work and commits everything together, ensuring the database moves from one consistent state to another in a single transaction.
How It Works
As the application loads, creates, modifies, and deletes domain objects, the Unit of Work registers them as new, dirty, or removed. It can do this explicitly, where code calls registerDirty, or implicitly, where a proxy or change tracker detects modifications. When the business operation completes, calling commit causes the Unit of Work to work out the right set of inserts, updates, and deletes, order them to respect foreign-key constraints, execute them within one database transaction, and handle optimistic concurrency checks. If anything fails, the whole transaction rolls back.
Batching changes this way reduces database round trips and guarantees that partial updates never leak out. Most ORMs implement a Unit of Work as their session or context object.
When to Use It
Use Unit of Work when a single business operation touches multiple objects that must be persisted atomically, when you want to reduce the number of database calls by batching writes, or when you need a central place to manage transaction boundaries and concurrency. It pairs naturally with repositories in domain-driven and layered designs.
Trade-offs
The pattern adds complexity around object tracking and the lifecycle of the unit, and long-lived units can hold many objects in memory and lengthen transactions, increasing lock contention. Implicit change tracking has overhead and can produce surprising writes if developers are unaware of what is tracked. It governs a single database transaction, so it does not by itself solve consistency across multiple services, where the Saga pattern is needed instead.
Related Patterns
Repository loads and stores the objects that a Unit of Work tracks, and the two are typically used together. Data Mapper performs the actual object-to-table writes the Unit of Work orchestrates. Saga addresses atomicity across service boundaries where a single database transaction cannot reach.
Example
Processing an order updates the order, decrements inventory, and writes a ledger entry. A Unit of Work tracks all three changes and commits them in one transaction. If the inventory update fails, the order and ledger changes roll back too, so the system never records an order without reserving stock.