Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Unit Of Work In ASP.Net Core

2025年09月23日に「Dev.to」が公開したITニュース「Unit Of Work In ASP.Net Core」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Unit of Workは、複数のデータベース操作を一つのトランザクションとしてまとめ、データの一貫性を保つデザインパターンだ。ASP.NET Coreでリポジトリパターンと併用し、全て成功か全て失敗かを保証することで、安全にデータ更新を管理できる。

出典: Unit Of Work In ASP.Net Core | Dev.to公開日:

ITニュース解説

アプリケーション開発において、データベースとのやり取りは非常に重要だ。特に、複数のデータベース操作をまとめて行い、その一連の操作がすべて成功するか、あるいはすべて失敗するかのどちらかにする仕組みは、データの整合性を保つ上で欠かせない。これを実現する設計パターンの一つが「Unit of Work(ユニット・オブ・ワーク)」だ。

Unit of Workパターンは、複数のデータ操作(主にリポジトリを介した操作)を一つの「作業単位」としてまとめ、この単位内で実行されたすべての操作を一つのトランザクションとして扱えるようにする。これにより、例えばオンラインショップで商品の注文処理を行う際に、「在庫の減少」「注文履歴の追加」「支払い情報の記録」といった複数の操作がすべて成功しなければ注文は完了せず、もし途中で一つでも失敗すれば、すべての操作が取り消されて元の状態に戻る、というように、データの不整合が起きないように保証できる。

このUnit of Workパターンは、通常「Repository(リポジトリ)」パターンと組み合わせて利用される。リポジトリパターンは、データベースへの直接的なアクセスロジックを抽象化し、データ操作を特定のデータ型(エンティティ)ごとの専用の窓口を通して行うようにする。例えば、商品に関する操作は「商品リポジトリ」、注文に関する操作は「注文リポジトリ」といった形で窓口を設ける。これにより、データベースの種類が変わってもアプリケーションの主要なロジックを変更することなく対応できるようになり、コードの見通しも良くなる。

この記事では、ASP.NET CoreというWebアプリケーション開発のフレームワークを使い、このUnit of WorkとRepositoryパターンをどのように実装するかを具体的に示している。まず、データベースに保存するデータの形を定義する「エンティティ」から見ていこう。ここでは、Product(商品)、Order(注文)、Log(ログ)という三つのエンティティが用意されている。これらは、それぞれデータベースのテーブルに対応するクラスであり、商品のIDや名前、注文のIDや日付、ログのメッセージなど、保存したい情報をプロパティとして持っている。

次に、データベースそのものとの接続や、エンティティをデータベースのテーブルとして扱うための中心的な役割を担う「DbContext」を設定する。ここではAppDbContextというクラスがそれで、ProductOrderLogそれぞれに対応するDbSetプロパティを持つ。DbSetは、特定のエンティティのコレクション(集合)を表し、これを通してデータベースへの追加、更新、削除などの操作を行う。

リポジトリは、これらのエンティティに対する具体的なデータベース操作をカプセル化する。IProductRepositoryIOrderRepositoryILogRepositoryというインターフェースで、各エンティティに対する「何ができるか」を定義し、ProductRepositoryOrderRepositoryLogRepositoryというクラスで「どのように実現するか」を実装する。例えばProductRepositoryは、商品のIDで検索したり、新しい商品を追加したり、既存の商品を更新・削除したりするメソッドを持つ。これらのリポジトリは、内部でAppDbContextのインスタンスを受け取り、それを使ってデータベース操作を実行する。

そして、いよいよUnit of Workパターンの核心部分だ。IUnitOfWorkというインターフェースが定義されており、このインターフェースはIProductRepositoryIOrderRepositoryILogRepositoryという各リポジトリのプロパティを持つ。これにより、IUnitOfWorkのインスタンスを一つ持つだけで、すべてのリポジトリにアクセスできるようになる。さらに、CompleteAsync()というメソッドが定義されており、これが呼ばれると、その時点までにリポジトリを介して行われたすべての変更が、データベースに一つのトランザクションとしてまとめて保存される。また、IDisposableインターフェースも実装されており、データベース接続などのリソースを適切に解放できるようになっている。

UnitOfWorkクラスがIUnitOfWorkインターフェースの実装であり、コンストラクタでAppDbContextと各リポジトリのインスタンスを受け取る。そして、CompleteAsync()メソッドの内部では_context.SaveChangesAsync()を呼び出す。これが、これまでに_contextAppDbContextのインスタンス)に記録されたすべての変更をデータベースに書き込み、一連の作業を完了させる中心的な処理となる。

これらのコンポーネントを実際にアプリケーションで使うためには、「依存性注入(Dependency Injection)」という仕組みを使って、必要な時に適切なインスタンスを提供できるように設定する。services.AddDbContextAppDbContextを設定し、services.AddScopedを使って各リポジトリとUnitOfWorkを登録する。AddScopedは、一つのWebリクエストの期間中、同じインスタンスが共有されるように設定するという意味だ。これにより、同じリクエスト内で複数のリポジトリがAppDbContextの同じインスタンスを共有し、Unit of Workによって一つのトランザクションとして扱われるようになる。

最後に、実際にこれらのパターンがどのように利用されるかを見てみよう。CheckoutControllerというコントローラでは、IUnitOfWorkのインスタンスをコンストラクタで受け取っている。Checkoutというメソッド内で、新しい商品、注文、ログのオブジェクトを作成し、_unitOfWork.Products.AddAsync(product)のように、Unit of Workを介して各リポジトリの追加メソッドを呼び出す。この時点ではまだデータベースへの書き込みは行われていない。すべての準備が整った後、await _unitOfWork.CompleteAsync()を呼び出すことで、これらすべての追加操作が「一つのまとまり」としてデータベースに保存される。もし、これらの操作の途中で何らかのエラーが発生した場合、CompleteAsync()が呼ばれずに処理が中断されるか、内部で例外が発生して、データベースへの変更は一切行われない。これにより、データの一貫性が完全に保たれるのだ。

このUnit of Workパターンを導入することで、データベースを予期せぬ不整合な状態から守り、より堅牢で信頼性の高いアプリケーションを構築できるようになる。複数のデータベース操作を扱うアプリケーションを開発する上で、非常に重要な設計手法の一つである。

関連コンテンツ

関連IT用語