1. ホーム
  2. asp.net

[解決済み] 汎用リポジトリを作成する利点と、各オブジェクトに固有のリポジトリを作成する利点は何ですか?

2022-06-19 04:25:45

質問

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();
}

各リポジトリは、例えばこのインターフェイスを実装することになります。

  • カスタマーリポジトリ:IRepository
  • プロダクトリポジトリ: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)

をいくつかの異なるサービス(Customer, Invoice, Account, etc.)から呼び出すことができます。 以下のように複数の場所に書くより、ずっとすっきりしているように思います。

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

特定のアプローチを使用する唯一の欠点は、Get*関数の多くの並べ替えに行き着くことですが、これは式ロジックをServiceクラスに押し上げるよりもまだ望ましいように思われます。

私は何を見逃しているのでしょうか?

どのように解決するのですか?

これは、Repositoryパターンそのものと同じくらい古い問題です。最近導入された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 を公開することは、まったく別の問題です。この話題については、おへそのボタンと同じくらい多くの意見があります。