オブジェクトごとに特定のリポジトリではなく、一般的なリポジトリを作成する利点は何ですか?


132

ASP.NET MVCアプリケーションを開発しており、現在、リポジトリ/サービスクラスを構築しています。すべてのリポジトリが実装する汎用のIRepositoryインターフェイスを作成することには、各リポジトリが独自のインターフェイスとメソッドのセットを持っているのに対して、大きな利点があるのか​​と思います。

たとえば、汎用のIRepositoryインターフェイスは次のようになります(この回答から取得)。

public interface IRepository : IDisposable
{
    T[] GetAll<T>();
    T[] GetAll<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter);
    T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
    void Delete<T>(T entity);
    void Add<T>(T entity);
    int SaveChanges();
    DbTransaction BeginTransaction();
}

各リポジトリはこのインターフェースを実装します、例えば:

  • CustomerRepository:IRepository
  • ProductRepository:IRepository

以前のプロジェクトで従った代替案は次のとおりです。

public interface IInvoiceRepository : IDisposable
{
    EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
    EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
    InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
    InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
    InvoiceEntity CreateInvoice();
    InvoiceLineEntity CreateInvoiceLine();
    void SaveChanges(InvoiceEntity); //handles inserts or updates
    void DeleteInvoice(InvoiceEntity);
    void DeleteInvoiceLine(InvoiceLineEntity);
}

2番目のケースでは、式(LINQまたはその他)は完全にリポジトリ実装に含まれます。サービスを実装している人は、呼び出すリポジトリ関数を知る必要があるだけです。

サービスクラスにすべての式の構文を記述してリポジトリに渡すことの利点はわかりません。これは、多くの場合、使いやすいLINQコードが複製されていることを意味しませんか?

たとえば、以前の請求システムでは、

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

いくつかの異なるサービス(顧客、請求書、アカウントなど)から。複数の場所で次のように書くよりも、はるかにきれいに見えます。

rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);

特定のアプローチを使用することで私が目にする唯一の欠点は、Get *関数の多くの順列が発生する可能性があることですが、これは式ロジックをServiceクラスにプッシュするよりも望ましいようです。

何が欠けていますか?


完全なORMで汎用リポジトリーを使用するのは役に立たないようです。これについては、ここで詳しく説明しまし
Amit Joshi

回答:


169

これは、リポジトリパターン自体と同じくらい古い問題です。IQueryableクエリの統一表現であるLINQの最近の導入により、このトピックについて多くの議論が生じています。

一般的なリポジトリフレームワークを構築するために一生懸命働いた後、私は特定のリポジトリを自分で好みます。どんなに巧妙なメカニズムを試しても、いつも同じ問題が発生しました。リポジトリはモデル化されるドメインの一部であり、そのドメインは一般的ではありません。すべてのエンティティを削除できるわけではなく、すべてのエンティティを追加できるわけではなく、すべてのエンティティにリポジトリがあるわけではありません。クエリは大きく異なります。リポジトリAPIはエンティティ自体と同じくらいユニークになります。

私がよく使用するパターンは、特定のリポジトリー・インターフェースを持つことですが、実装の基本クラスです。たとえば、LINQ to SQLを使用すると、次のことができます。

public abstract class Repository<TEntity>
{
    private DataContext _dataContext;

    protected Repository(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    protected IQueryable<TEntity> Query
    {
        get { return _dataContext.GetTable<TEntity>(); }
    }

    protected void InsertOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().InsertOnCommit(entity);
    }

    protected void DeleteOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
    }
}

DataContext選択した作業単位に置き換えます。実装例は次のようになります。

public interface IUserRepository
{
    User GetById(int id);

    IQueryable<User> GetLockedOutUsers();

    void Insert(User user);
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(DataContext dataContext) : base(dataContext)
    {}

    public User GetById(int id)
    {
        return Query.Where(user => user.Id == id).SingleOrDefault();
    }

    public IQueryable<User> GetLockedOutUsers()
    {
        return Query.Where(user => user.IsLockedOut);
    }

    public void Insert(User user)
    {
        InsertOnCommit(user);
    }
}

リポジトリのパブリックAPIでは、ユーザーを削除できないことに注意してください。また、公開IQueryableはワームの完全な他の缶です-そのトピックにはへそと同じくらい多くの意見があります。


9
真剣に、素晴らしい答え。ありがとう!
ビープビープ音

5
では、これでIoC / DIをどのように使用しますか?フルであなたのパターンに関しては(私はのIoCで初心者です)私の質問:stackoverflow.com/questions/4312388/...
ダン

36
「リポジトリはモデル化されるドメインの一部であり、そのドメインは一般的ではありません。すべてのエンティティを削除できるわけではなく、すべてのエンティティを追加できるわけではありません。すべてのエンティティにリポジトリがあるわけではありません。」
adamwtiko

私はこれが古い答えであることを知っていますが、意図的にRepositoryクラスからUpdateメソッドを除外することに興味があります。これを行うクリーンな方法を見つけるのに苦労しています。
rtf 2015

1
オールディーだけどグッディー。この投稿は信じられないほど賢明であり、読み、読み直す必要がありますが、裕福な開発者は全員です。@BryanWattsに感謝します。私の実装は通常dapperベースですが、前提は同じです。機能にオプトインするドメインを表す特定のリポジトリを持つベースリポジトリ。
pimbrouwers

27

私は実際にはブライアンの投稿に少し同意しません。私は彼が正しいと思います。最終的にはすべてが非常にユニークである、などです。しかし、同時に、それらのほとんどは設計時に明らかになります。モデルの開発中に汎用リポジトリを取得して使用すると、アプリを非常にすばやく起動でき、見つけたときに特定性を高めるためにリファクタリングできます。そうする必要があります。

そのため、そのような場合は、CRUDスタック全体を備えた汎用のIRepositoryを頻繁に作成しました。これにより、APIをすぐに利用できるようになり、UIを使って人々がプレイできるようになり、統合とユーザー受け入れテストを並行して行うことができます。次に、レポなどで特定のクエリが必要であることがわかったので、必要に応じて特定の依存関係とその依存関係を置き換え、そこから移動します。1つの基礎となる実装。作成と使用は簡単です(インメモリdbや静的オブジェクト、モックオブジェクトなどにフックすることもできます)。

とは言っても、私が最近始めたのは行動を壊すことです。したがって、IDataFetcher、IDataUpdater、IDataInserter、およびIDataDeleter(たとえば)のインターフェイスを実行する場合、インターフェイスを介して要件を定義するために組み合わせて、それらの一部またはすべてを処理する実装を持つことができます。それでも、アプリを構築しているときに使用するdo-it-all実装を注入します。

ポール


4
返信@Paulをありがとう。実際にそのアプローチも試しました。私が最初に試みた方法を一般的に表現する方法を理解できませんでしたGetById()。私が使用する必要がありますIRepository<T, TId>GetById(object id)または仮定して利用しますかGetById(int id)?複合キーはどのように機能しますか?IDによる一般的な選択が価値のある抽象化であるかどうか疑問に思いました。そうでない場合、汎用リポジトリは他に何が不適切に表現することを余儀なくされますか?これは、インターフェースではなく、実装の抽象化の背後にある推論の行でした。
ブライアンワッツ

10
また、汎用クエリメカニズムはORMの役割です。リポジトリは、一般的なクエリメカニズムを使用して、プロジェクトのエンティティに特定のクエリを実装する必要があります。リポジトリのコンシューマは、レポートなどの問題ドメインの一部でない限り、独自のクエリを作成するよう強制されるべきではありません。
ブライアンワッツ

2
@Bryan-GetById()について。私はFindById <T、TId>(TId id);を使用しています。したがって、repository.FindById <Invoice、int>(435);のような結果になります。
ジョシュアヘイズ

率直に言って、私は通常、フィールドレベルのクエリメソッドをジェネリックインターフェイスに配置しません。あなたが指摘したように、すべてのモデルが単一のキーでクエリされる必要があるわけではなく、場合によっては、アプリがIDで何かをまったく取得しないことがあります(たとえば、DB生成の主キーを使用していて、ログイン名などの自然キー)。クエリメソッドは、リファクタリングの一環として構築した特定のインターフェイスで進化します。
ポール、

13

オーバーライド可能なメソッドシグネチャを使用して、ジェネリックリポジトリ(または正確な動作を指定するためのジェネリックリポジトリのリスト)から派生する特定のリポジトリを好みます。


小さなスニペットっぽい例を提供していただけませんか?
ヨハンゲレル

@Johann Gerellいいえ、もうリポジトリを使用しないので。
Arnis Lapsa

リポジトリから離れている今、何を使用していますか?
クリス・

@Chrisは、豊富なドメインモデルを持つことに重点を置いています。アプリケーションの入力部分が重要です。すべての状態変化が注意深く監視されている場合、十分に効率的であれば、データの読み取り方はそれほど重要ではありません。そのため、NHibernate ISessionを直接使用します。リポジトリレイヤーの抽象化がなければ、熱心な読み込みやマルチクエリなどを指定する方がはるかに簡単です。本当に必要な場合は、ISessionをモックアウトするのも難しくありません。
Arnis Lapsa、2011年

3
@JesseWebb Naah ...リッチドメインモデルを使用すると、クエリロジックが大幅に簡略化されます。たとえば、何かを購入したユーザーを検索したい場合は、users.Join(x => Orders、何か、linq)の代わりにusers.Where(u => u.HasPurchasedAnything)を検索するだけです。Where( order => order.Status == 1).Join(x => x.products).Where(x .... etc etc etc ...... blah blah blah
Arnis Lapsa

5

特定のリポジトリーによってラップされた汎用リポジトリーを用意します。そうすれば、パブリックインターフェースを制御できますが、汎用のリポジトリを使用することでコードを再利用できるという利点があります。


2

パブリッククラスUserRepository:リポジトリ、IUserRepository

インターフェイスの公開を避けるために、IUserRepositoryを注入するべきではありません。人々が言っ​​たように、完全なCRUDスタックなどは必要ないかもしれません。


2
これは答えというよりコメントのように見えます。
Amit Joshi、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.