しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC 3 開発入門 (4) - リポジトリパターンを適用する

ASP.NET MVC 3 開発入門 - インデックス

前回作成した MvcVideoContext を直接コントローラから利用しても問題は無いのですが、コントローラに直接 LINQ でクエリを書いてしまうと依存関係が強くなりすぎて単体テストが行いにくくなってしまいます。MVC アーキテクチャの利点としてテストが行いやすいという点がありますので、今回はリポジトリパターンを適用してビジネスロジックを別のクラスとして分離させ、コントローラとモデルの間に挟むことでテストが容易に行える形で実装してみましょう。

まずは Video クラスのリポジトリを作成するのですが、リポジトリパターンでは先にデータアクセスのメソッドを定義したインターフェースを作成します。インターフェースを作成することにより、テスト時にモッククラスを簡単に作成することが出来るようになります。

それでは IVideoRepository インターフェースを追加しましょう。Models フォルダ内に追加するので、フォルダを右クリックして表示されるメニューから「追加」サブメニュー内の「新しい項目」をクリックすると、以下のようなダイアログが表示されますので一覧からインターフェースを選択し、名前には IVideoRepository と入力して追加をクリックします。

すると Models フォルダ内に IVideoRepository.cs ファイルが追加されているはずです。これからインターフェースを作成していくのですが、その前に今回用意する必要のあるメソッドをあげてみると

  • Find
    • プライマリキーを使ってエンティティを取得する
  • GetAll
    • 存在するエンティティを全て取得する
  • GetPagedList
    • ページング用にページ番号と個数を指定して取得する
  • Add
    • テーブルにエンティティを追加する
  • Remove
    • テーブルから指定されたエンティティを削除する
  • Save
    • 変更をテーブルへコミットする

と言ったところだと思います。リポジトリはそのエンティティ専用になるので、特別な取得を行うメソッドを追加しても問題ありません。ジェネリックを使って共通のインターフェースと抽象クラスとして IRepository や Repository などと名前を付けて用意してもいいですね。

実際に上記のメソッドを持つインターフェースを作成すると以下のようになります。

public interface IVideoRepository
{
    Video Find(int id);
    IList<Video> GetAll();
    IList<Video> GetPagedList(int page, int count);
    void Add(Video video);
    void Remove(Video video);
    void Save();
}

インターフェースが完成したので、次は VideoRepository クラスを作成しましょう。このクラスが実際に、今回は MvcVideoContext クラスを経由してデータベースへのアクセスを行います。

VideoRepository クラスは当然ながら IVideoRepository インターフェースを実装するのですが、フィールドに MvcVideoContext のインスタンスを追加して LINQ を使っていきましょう。素直に実装した場合は以下のようなコードになると思います。

public class VideoRepository : IVideoRepository
{
    private readonly MvcVideoContext _context = new MvcVideoContext();

    public Video Find(int id)
    {
        return _context.Videos.Find(id);
    }

    public IList<Video> GetAll()
    {
        return _context.Videos.ToList();
    }

    public IList<Video> GetPagedList(int page, int count)
    {
        // 2011/03/01 OrderBy を追加
        //return _context.Videos.Skip((page - 1) * count).Take(count).ToList();
        return _context.Videos.OrderBy(p => p.Title).Skip((page - 1) * count).Take(count).ToList();
    }

    public void Add(Video video)
    {
        _context.Videos.Add(video);
    }

    public void Remove(Video video)
    {
        _context.Videos.Remove(video);
    }

    public void Save()
    {
        _context.SaveChanges();
    }
}

特に説明も不要なぐらいシンプルな実装になりました。単体テストを行う場合には IVideoRepository を継承したクラス、例えば MockVideoRepository などと名前を付けたクラスを準備すればいいですね。モッククラスは Moq.dll など非常に便利なライブラリが用意されているので、非常に簡単に作ることが出来ます。

同様に ITagRepository インターフェースと TagRepository クラスも作成していきましょう。コメントに関してはコメントのみを一覧表示するという場面が存在しないので用意しないことにします。今回はタグクラウドを実装しますので専用の取得メソッドだけ実装しておきましょう。

public interface ITagRepository
{
    ILookup<string, Tag> GetTagCloud();
}

// 2011/02/20 インターフェース実装していなかったのを修正
public class TagRepository : ITagRepository
{
    private readonly MvcVideoContext _context = new MvcVideoContext();

    public ILookup<string, Tag> GetTagCloud()
    {
        return _context.Tags.ToLookup(p => p.Name);
    }
}

冗長に思えるリポジトリパターンですが単体テスト時にその力を発揮してくれますし、ビジネスロジックをコントローラから排除する事になりますので、MVC の観点からも分離させておく意味はあると思います。

次回からはようやくコントローラの作成を行って行きたいと思います。お疲れ様でした。