Some patterns don't announce themselves with an obvious interface or a textbook-matching class name. Sometimes they're just... there, quietly, in code that looks like ordinary sequential logic. This article walks through a real example: a multi-step save operation that turns out to be a working instance of the Unit of Work pattern, even though nothing in the code is labeled that way.
The Code in Question
A salary/batch processing screen performs its save in two steps, calling two separate methods:
csharp
var batchResult = InsertBatchRecord(batchModel, connection, transaction);
if (batchResult.Success)
{
var updateResult = UpdateRelatedRecords(batchResult.GeneratedId, relatedModel, connection, transaction);
}
Two details stand out immediately: both calls share the exact same connection and transaction objects, and the second call only executes if the first one succeeded.
What's Actually Happening Here
Sharing the same connection and transaction across both calls means these two database operations aren't independent of each other — they're bound together as a single logical unit. Neither InsertBatchRecord nor UpdateRelatedRecords opens its own separate connection or starts its own separate transaction; they both operate within one shared transactional context established somewhere earlier in the calling code.
The conditional check (if (batchResult.Success)) means the second operation is deliberately gated on the first one's outcome. This isn't accidental sequencing — it's a guard against doing the second write if the first one didn't succeed.
Put together, this is the essential shape of the Unit of Work pattern: treating multiple related database operations as a single, all-or-nothing unit, so that a partial failure doesn't leave the database in an inconsistent state.
Why This Matters: What Happens on Failure
The real value of this structure shows up specifically when something goes wrong. Consider what should happen if UpdateRelatedRecords fails after InsertBatchRecord already succeeded within the same transaction:
csharp
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 the same transaction object, a rollback at any point undoes everything done within that transaction so far — including the batch record insert that technically succeeded moments earlier. Without this shared-transaction structure, a failure in the second step would leave the first step's data permanently saved, with no corresponding update — an inconsistent, partially-completed state that could be difficult to detect and even harder to clean up later, especially in a payroll context where a stray batch record without its related update could cause real downstream confusion.
Why This Is Easy to Overlook as "Just a Pattern"
Code like this often doesn't get recognized as implementing a named pattern, because it doesn't involve an interface, a dedicated UnitOfWork class, or any explicit pattern-related vocabulary. It's simply two method calls sharing a connection and transaction, with a success check between them. But the underlying intent — grouping related operations so they succeed or fail together — is precisely what the Unit of Work pattern describes, regardless of whether the code uses that terminology or a more formal implementation involving a dedicated coordinating class.
Formal implementations of Unit of Work often introduce a dedicated class responsible for tracking all pending changes and committing them together:
csharp
public class UnitOfWork
{
private readonly OracleTransaction _transaction;
public bool InsertBatchRecord(BatchModel model) { /* uses _transaction */ }
public bool UpdateRelatedRecords(int batchId, RelatedModel model) { /* uses _transaction */ }
public void Commit() => _transaction.Commit();
public void Rollback() => _transaction.Rollback();
}
This formalized version makes the pattern more explicit and reusable across different save operations, but the simpler version — passing a shared connection and transaction directly into sequential method calls — achieves the same core guarantee for a single, specific save operation. It's a smaller-scale, less formalized instance of the same underlying idea.
A Practical Question for Reviewing Multi-Step Saves
When reviewing any code that performs more than one database write as part of a single logical operation, it's worth asking: if the second (or third, or later) write fails, what happens to the writes that already succeeded? If the honest answer is "they stay saved, creating a partial, inconsistent result," that's a signal the operation needs a shared transaction wrapping all the steps together — exactly the structure already present in this example.
Takeaway
The Unit of Work pattern doesn't require a dedicated class or explicit pattern-vocabulary to be genuinely present in code. Sharing a single connection and transaction across multiple related database operations, combined with a rollback on any failure, is the pattern's essential behavior, however plainly it's written. Recognizing it in ordinary-looking sequential code — rather than only in formalized, textbook implementations — makes it easier to spot both where the pattern is already protecting data consistency, and where a multi-step save might be missing that protection entirely.
Top comments (1)
the shared transaction is the key part, yeah. on that salary batch screen, does the UI show a single save failure when the second write fails, or can it make the first step look successful before the rollback? that distinction matters a lot to whoever is reconciling payroll.