The Code Under Examination
Some patterns appear without an interface, a dedicated class, or any pattern-related vocabulary. They are simply present in code that reads as ordinary sequential logic.
This article examines a real example: a two-step database save that implements the Unit of Work pattern, even though nothing in the code is labeled that way.
A batch processing save operation performs its work across two method calls:
var batchResult = InsertBatchRecord(
batchModel,
connection,
transaction);
if (batchResult.Success)
{
var updateResult = UpdateRelatedRecords(
batchResult.GeneratedId,
relatedModel,
connection,
transaction);
}
Two structural details are worth noticing immediately:
Both calls receive the identical
connectionobject.Both calls receive the identical
transactionobject.The second call is conditioned entirely on the first one's success.
These details are more important than whether the code contains a class named UnitOfWork.
What Is the Unit of Work Pattern?
The Unit of Work pattern coordinates multiple related database operations as a single logical unit.
The basic idea is:
Either all related changes are successfully committed, or the changes are rolled back together.
This prevents a multi-step operation from leaving the database in a partially completed state.
For example, suppose a payroll process needs to:
Create a batch record.
Update related payroll records.
If the first operation succeeds but the second fails, committing only the first operation can leave incomplete data.
A shared transaction allows both operations to succeed or fail together.
Interpreting the Structure
Passing the same connection and transaction into both calls means these operations aren't independent database writes.
They are deliberately bound together within one shared transactional context, established somewhere earlier in the calling code rather than opened separately inside each method.
The success check also prevents the second operation from proceeding when the first operation did not complete successfully.
Together, these behaviors represent the essential idea behind a Unit of Work:
Logical Operation
|
↓
┌───────────────────────┐
│ Shared Transaction │
│ │
│ Insert Batch Record │
│ ↓ │
│ Update Related Data │
│ │
└───────────┬───────────┘
↓
Commit/Rollback
The important part is not the class name. It is the transactional behavior.
Where the Value Becomes Concrete: Handling Failure
The pattern's purpose becomes especially clear when something fails.
Consider the full operation, including commit and rollback handling:
try
{
var batchResult = InsertBatchRecord(
batchModel,
connection,
transaction);
if (batchResult.Success)
{
var updateResult = UpdateRelatedRecords(
batchResult.GeneratedId,
relatedModel,
connection,
transaction);
if (updateResult.Success)
{
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
else
{
transaction.Rollback();
}
}
catch (Exception)
{
transaction.Rollback();
throw;
}
Because both operations share one transaction, a rollback undoes everything performed within that transaction.
That includes a batch record insert that had already technically succeeded moments earlier.
Without this shared-transaction design, a failure in the second step could leave the first step permanently committed:
Insert Batch Record
↓
SUCCESS
↓
Update Related Records
↓
FAILURE
↓
What happens?
↓
First operation remains committed
Second operation is not applied
That can leave the database in a partially completed state.
In a payroll or batch-processing context, an orphaned record like this can be difficult to detect and even harder to reconcile afterward.
With a shared transaction:
Insert Batch Record
↓
SUCCESS
↓
Update Related Records
↓
FAILURE
↓
ROLLBACK
↓
Both changes are undone
This is the practical value of grouping related operations into one unit.
Why This Rarely Gets Labeled as a Pattern
Code structured this way typically doesn't get recognized as an instance of a named pattern because it lacks:
An interface
A dedicated
UnitOfWorkclassPattern-related terminology
A formal abstraction around the transaction
It may simply appear as two method calls sharing a connection and transaction, followed by a success check.
However, the underlying intent is what matters.
Related operations are being treated as one logical unit, and a failure causes the shared transaction to be rolled back.
That is the core behavior associated with the Unit of Work pattern.
Formalizing the Unit of Work
A more formal implementation often introduces a dedicated coordinating class responsible for managing related operations and committing them together.
For example:
public class UnitOfWork
{
private readonly OracleTransaction _transaction;
public bool InsertBatchRecord(BatchModel model)
{
// Uses _transaction
return true;
}
public bool UpdateRelatedRecords(
int batchId,
RelatedModel model)
{
// Uses _transaction
return true;
}
public void Commit()
{
_transaction.Commit();
}
public void Rollback()
{
_transaction.Rollback();
}
}
This makes the pattern explicit and can make the coordination logic reusable across multiple operations.
However, the simpler approach of passing a shared connection and transaction directly through sequential calls can provide the same core transactional behavior for a specific operation.
The abstraction is different, but the underlying idea is similar:
Formal Unit of Work
↓
Dedicated object coordinates work
↓
Shared transaction
↓
Commit or rollback
versus:
Simple implementation
↓
Caller coordinates work
↓
Shared transaction
↓
Commit or rollback
The important distinction is between implementing the transactional behavior and introducing a reusable abstraction around that behavior.
Unit of Work vs. Database Transaction
These concepts are related but should not be treated as exactly the same thing.
A database transaction is the mechanism that provides transactional guarantees at the database level.
A Unit of Work is an application-level pattern for coordinating a set of related changes as one logical operation.
A simplified relationship is:
Application
↓
Unit of Work
↓
Database Transaction
↓
Database
The Unit of Work can determine which operations belong together, while the transaction provides the commit and rollback behavior.
In some applications, the Unit of Work may also track entity changes, coordinate repositories, and manage transaction boundaries.
A Question Worth Asking During Code Review
When reviewing code that performs more than one database write as part of a single logical operation, ask:
If a later write in the sequence fails, what happens to the writes that already succeeded?
If the answer is that earlier writes remain committed, the operation may be vulnerable to a partial update.
For example:
Operation 1 → SUCCESS → COMMITTED
Operation 2 → FAILURE
Operation 3 → NOT EXECUTED
This can result in an inconsistent application state.
If all operations are logically part of one unit, they should generally be evaluated together and protected by an appropriate transaction boundary.
Recognizing the Pattern in Existing Code
When reviewing an unfamiliar codebase, look for these signals:
Shared Transaction
Multiple database operations receive the same transaction object.
InsertBatchRecord(connection, transaction);
UpdateRelatedRecords(connection, transaction);
Coordinated Commit
A commit happens only after all required operations succeed.
if (allOperationsSucceeded)
{
transaction.Commit();
}
Coordinated Rollback
A failure causes the transaction to roll back.
if (!operationSucceeded)
{
transaction.Rollback();
}
Logical Grouping
The operations together represent one business operation rather than unrelated database updates.
These signals can reveal a Unit of Work-like structure even when there is no class named UnitOfWork.
Important Considerations
A shared transaction is not automatically the correct solution for every set of database operations.
The operations should belong to the same logical consistency boundary.
For example, if two operations are completely independent, forcing them into a single transaction can unnecessarily increase transaction duration and resource usage.
Long-running transactions can also increase:
Lock duration
Resource consumption
Blocking
Deadlock risk
Recovery complexity
The transaction boundary should therefore reflect the actual business operation being protected.
Takeaway
The Unit of Work pattern doesn't require a dedicated class or explicit naming to be genuinely present in a codebase.
Sharing a single transaction across related database operations, coordinating their success, and rolling back the unit when a required operation fails represents the pattern's essential behavior.
A formal UnitOfWork class can make that behavior more explicit and reusable, but the underlying concept can also appear in straightforward sequential code.
Learning to recognize the pattern in ordinary database code makes it easier to identify where transactional protection already exists and where a multi-step operation might otherwise leave partially committed data.
The key question during a code review is simple:
If one operation fails, can the earlier operations safely be left committed?
If the answer is no, the related operations need a clearly defined transaction boundary.
Join the conversation! Your thoughts help the community grow.