21

リポジトリパターンを使用するときに、複雑なオブジェクトグラフの遅延読み込みの問題を適切に処理する方法を考えています。これは私が推測するORM固有の問題ではありません。

初挑戦:

public interface IProductRepository : IRepository<Product>
{
  Product GetById(int id);
  IProductRepository WithCustomers();
}

これは問題なく機能しますが、常に自分自身を繰り返す必要があります(あらゆる場所のリポジトリ実装でカスタムの「With」メソッドを作成します)。

次のアプローチ:

public interface IRepository<T> where T : IAggregateRoot
{
  ...
  void With(Expression<Func<T, object>> propToExpand);
}

Withメソッドは、必要なエンティティを取得するときにどの小道具を熱心にロードする必要があるかを見つけるために後で使用されるプライベートコレクションにアイテムを追加します。

この種はうまくいき、大丈夫です。しかし、私は使用法が嫌いです:

productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);

基本的に-問題は連鎖がないことです。私はそれをこのようにしたいと思います:

var product = productRepository
  .With(x=>x.Customer)
  .With(x=>x.Price)
  .With(x=>x.Manufacturer)
  .GetById(id);

私はこれを達成できませんでした。できたとしても、そのソリューションがエレガントかどうかはわかりません。

これは、私が基本的な何かを見逃しているという考えにつながります(どこにも例がない)。これを処理するさまざまな方法はありますか?ベストプラクティスは何ですか?

4

7 に答える 7

10

興味深い問題であり、あなたがこれに問題を抱えている最初の人ではないと確信しています(私は絶対に持っています)。

私にとって、本当の問題は、熱心な読み込みロジックをどこに配置したいかということです。

クライアントコードのリポジトリの外

var product = productRepository
.With(x=>x.Customer)
.With(x=>x.Price)
.With(x=>x.Manufacturer)
.GetById(id);

私はそれが良いソフトウェア設計だとは思いません。そのような構成がアプリ全体に散らばっている場合、これは「1000カットによる死」を引き起こす可能性があるようです。

またはリポジトリ内。例:

interface IProductRepository {
    Product GetById(int id);
    Product GetByIdWithCustomers(int i);
}

したがって、クライアントコードは次のようになります。

var product = productRepository.GetByIdWithCustomers(id);

通常、基本的なCRUD操作のみが定義されたBaseRepositoryを1つ作成します。

public class BaseRepository<TEntity, TPrimaryKey> {
    public void Save(TEntity entity) { ... }
    public void Delete(TEntity entity) { ... }
    public TEntity Load(TPrimaryKey id) { ... } // just gets the entity by primary key
}

次に、ドメインオブジェクトをフェッチするための特定のメソッドを提供するために、この基本クラス/インターフェイスを拡張します。あなたのアプローチはやや似た方向に進んでいるようです。

public class MediaRepository : BaseRepository<Media, int> {
    public long CountMediaWithCategories() { ... }
    public IList<Media> MediaInCategories(IList<Category> categories) { .... }
}

良い点:すべてのORMのもの(構成の読み込み、深さのフェッチなど)はRepositoryクラスにカプセル化され、クライアントコードは結果セットを取得するだけです。

私はあなたがやろうとしているように非常に一般的なリポジトリを使って作業しようとしましたが、ほとんどの場合、ドメインオブジェクト用の特定のクエリとリポジトリを作成することになりました。

于 2009-10-26T11:03:40.057 に答える
2
var product = productRepository
 .With(x=>x.Customer)
 .With(x=>x.Price)
 .With(x=>x.Manufacturer)
 .GetById(id);

上記のようにオブジェクトグラフのクエリ深度を決定したいというあなたの希望は理解できますが、もっと簡単な方法があると思います。IDで(顧客、価格、メーカーを含む)製品を返品する代わりに、単に製品を返品するのはどうですか?他のすべてのものは、製品の遅延ロードされたプロパティです。

データアクセス層のPOCOオブジェクトモデルによる「連鎖」によって、この「完全グラフのアクセシビリティ」を実現します。このように、一度にどれだけ熱心にロードされたデータを引き出す必要があるかを知る必要はありません。オブジェクトグラフから必要なものを尋ねるだけで、モデルはロードされたものとDALから追加で回復する必要があるものを知っています。これらの 3つの 答えを見てください-私はそこで私のアプローチを説明しようとします。さらに詳しい説明が必要な場合はお知らせください。この回答を編集します。

于 2009-11-14T17:37:15.427 に答える
2

これは古い質問ですが、おそらく誰かを助けることができます。私は良いアプローチを見つけるためにしばらく時間を費やしました、これが私がC#で見つけたものです:

IRepository.cs:

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , params Expression<Func<TEntity, object>>[] properties);
}

Repository.cs

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{    
    private readonly DbSet<TEntity> _dbset;

    public Repository(DbSet<TEntity> dbset)
    {
        _dbset = dbset;
    }

    public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> where
                              , Expression<Func<TEntity, object>>[] properties)
    {
        if (where == null) 
            throw new ArgumentNullException(nameof(where));    
        if (properties == null) 
            throw new ArgumentNullException(nameof(properties));

        var query = _dbset as IQueryable<TEntity>; // _dbSet = dbContext.Set<TEntity>()

        query = properties
                   .Aggregate(query, (current, property) => current.Include(property));

        return query.AsNoTracking().Where(where).ToList();
    }
}

使い方:

var repository = new Repository<User>();
var users = repository.GetAll(p => p.Id == 1, d => d.Address, d => d.Carts);

参照:リンク

于 2017-01-27T04:58:18.387 に答える
0

あなたがやろうとしていることは理解できますが、基本的なリポジトリパターンをいくらか超えています。

最小限のリポジトリインターフェイスには、次のメソッドが含まれる場合があります。

  • GetById
  • 追加
  • 削除する

その上にメソッドを追加すると、インターフェイスがすべての集約ルートに対して必ずしも意味をなさない状況に遭遇し始めます。

完全に美しいAPIを使用することが不可能な場合もあります。あなたが持っているものがあなたにとって「十分に」うまくいくなら、私はそれで行きます。プログラムするためのより良いAPIを提供するためにリポジトリパターンから離れる必要がある場合は、それを実行してください。

リポジトリパターンは、万能/最終的な解決策ではありません。別のソリューションが必要になる場合があります。

于 2009-10-25T11:38:08.087 に答える
0

リポジトリの外部で必要なすべてのインクルードを示したい場合は、各ジェネリックメソッドのオプションのパラメーター(C#)を一覧表示できます。

TEntity Find(Func<TEntity, bool> expression, params string[] eagerLoads);

次に、クライアント層で:

IProductRepository.Find(x => x.Id == id, "Customer", "Price")

タイプセーフにしたい場合は、エンティティを列挙します。

public enum BusinessEntities { Customer, Price, Manufacturer }

IProductRepository.Find(x => x.Id == id, BusinessEntities.Customer.ToString(), BusinessEntities.Price.ToString())

具体的に何が欲しいのかを尋ねるのはクライアントの責任だと思います。GenericRepositoryは基本的なCRUDを処理する必要があります。

于 2011-12-14T17:28:04.027 に答える
0

で、次のBaseRepository.csメソッドを作成できます。

public async Task<IEnumerable<T>> GetWithChild(string child)
{
    return await _entities.Include(child).ToListAsync();
}

私のAPIでは、サービスレイヤーも実装しましたが、APIからこのメソッドを呼び出して、ロードする変数の名前を渡すだけです。

明らかに、あなたの状況では、さらにいくつかの文字列を含める必要があります。

于 2017-10-11T14:34:54.793 に答える
0

以前に回答を投稿しましたが、それでも解決策に満足していませんでした。したがって、ここに、より良い解決策があります。

BaseRepository.csで

public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
      IQueryable<T> query = _entities;

      query = properties.Aggregate(query, (current, property) => current.Include(property));

      return await query.AsNoTracking().ToListAsync();
}

次のように簡単に使用できます

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
于 2017-10-11T15:19:48.803 に答える