web-dev-qa-db-ja.com

汎用リポジトリを作成する利点と、各オブジェクトの特定のリポジトリを作成する利点

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

たとえば、一般的なIRepositoryインターフェイスは次のようになります( this answer から取得):

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クラスにプッシュするよりも望ましいようです。

私は何が欠けていますか?

130
Beep beep

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

汎用リポジトリフレームワークを構築するために非常に懸命に働いた後、私は特定のリポジトリを好みます。どのような巧妙なメカニズムを試したとしても、私はいつも同じ問題に陥りました。リポジトリはモデル化されるドメインの一部であり、そのドメインは汎用的ではありません。すべてのエンティティを削除できるわけではなく、すべてのエンティティを追加できるわけでも、すべてのエンティティにリポジトリがあるわけでもありません。クエリは大きく異なります。リポジトリ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を公開することは、他のすべてのワームの缶です-そのトピックについての腹ボタンと同じくらい多くの意見があります。

164
Bryan Watts

私は実際にブライアンの投稿に少し同意しません。私は彼が正しいと思う、最終的にはすべてが非常にユニークであるなど。しかし同時に、設計の際にそのほとんどが出てくるので、モデルの開発中に汎用リポジトリを作成して使用すると、アプリを非常に迅速に作成でき、その後、より具体的にリファクタリングできます。そうする必要があります。

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

そうは言っても、私が最近やろうとしているのは、動作を分割することです。したがって、IDataFetcher、IDataUpdater、IDataInserter、およびIDataDeleterのインターフェイスを実行する場合(たとえば)、インターフェイスを使用して要件を定義するためにミックスアンドマッチを実行し、それらの一部またはすべてを処理する実装を持つことができます。私がアプリを構築している間に使用するために、does-it-all実装をまだ注入します。

ポール

26
Paul

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

12
Arnis Lapsa

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

5
Peter

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

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

1
Ste