私はまだこれに苦労していますが、回答として投稿したいだけでなく、これに関するフィードバックを受け入れます (そして望んでいます)。
例ではGetProductsByCategory(int id)
まずは最初の必要性から考えてみましょう。コントローラー、おそらく CategoryController をヒットしたので、次のようになります。
public CategoryController(ICategoryService service) {
// here we inject our service and keep a private variable.
}
public IHttpActionResult Category(int id) {
CategoryViewModel model = something.GetCategoryViewModel(id);
return View()
}
ここまでは順調ですね。ビューモデルを作成する「何か」を宣言する必要があります。簡単にして言いましょう:
public IHttpActionResult Category(int id) {
var dependencies = service.GetDependenciesForCategory(id);
CategoryViewModel model = new CategoryViewModel(dependencies);
return View()
}
わかりました、依存関係とは何ですか? カテゴリツリー、製品、ページ、合計製品数などが必要になる場合があります。
したがって、これをリポジトリの方法で実装すると、多かれ少なかれ次のようになります。
public IHttpActionResult Category(int id) {
var products = repository.GetCategoryProducts(id);
var category = repository.GetCategory(id); // full details of the category
var childs = repository.GetCategoriesSummary(category.childs);
CategoryViewModel model = new CategoryViewModel(products, category, childs); // awouch!
return View()
}
代わりに、サービスに戻ります:
public IHttpActionResult Category(int id) {
var category = service.GetCategory(id);
if (category == null) return NotFound(); //
var model = new CategoryViewModel(category);
return View(model);
}
はるかに優れていますが、正確には何が入っていservice.GetCategory(id)
ますか?
public CategoryService(ICategoryRespository categoryRepository, IProductRepository productRepository) {
// same dependency injection here
public Category GetCategory(int id) {
var category = categoryRepository.Get(id);
var childs = categoryRepository.Get(category.childs) // int[] of ids
var products = productRepository.GetByCategory(id) // this doesn't look that good...
return category;
}
}
別のアプローチである作業単位を試してみましょう。エンティティ フレームワークを UoW とリポジトリとして使用するので、それらを作成する必要はありません。
public CategoryService(DbContext db) {
// same dependency injection here
public Category GetCategory(int id) {
var category = db.Category.Include(c=> c.Childs).Include(c=> c.Products).Find(id);
return category;
}
}
したがって、ここではメソッド構文の代わりに「クエリ」構文を使用していますが、独自の複合体を実装する代わりに、ORM を使用できます。また、すべてのリポジトリにアクセスできるため、サービス内で Unit of work を実行できます。
次に、必要なデータを選択する必要があります。おそらく、エンティティのすべてのフィールドは必要ありません。
これが実際に起こっていることがわかる最適な場所は ViewModel です。各 ViewModel はそれ自身のデータをマップする必要があるかもしれないので、サービスの実装をもう一度変更しましょう。
public CategoryService(DbContext db) {
// same dependency injection here
public Category GetCategory(int id) {
var category = db.Category.Find(id);
return category;
}
}
すべての製品と内部カテゴリはどこにありますか?
ViewModel を見てみましょう。これはデータを値にのみマップすることを覚えておいてください。ここで何か他のことをしている場合は、ViewModel に責任を与えすぎている可能性があります。
public CategoryViewModel(Category category) {
Name = category.Name;
Id = category.Id;
Products = category.Products.Select(p=> new CategoryProductViewModel(p));
Childs = category.Childs.Select(c => c.Name); // only childs names.
}
CategoryProductViewModel
あなたは今自分で想像することができます。
BUT (なぜ常に but があるのですか??)
3 db のヒットを実行しており、検索のためにすべてのカテゴリ フィールドを取得しています。また、遅延読み込みを有効にする必要があります。本当の解決策ではありませんか?
これを改善するために、find を where... で変更できますが、これはSingle
orFind
を ViewModel にデリゲートし、IQueryable<Category>
正確に 1 であることがわかっている を返します。
「私はまだ苦労していますか?」と言ったのを覚えていますか?これが主な理由です。これを修正するには、サービスから正確に必要なデータを返す必要があります (また、ご存知のように ..... ご存知のとおり、ViewModel です)。
それでは、コントローラに戻りましょう:
public IHttpActionResult Category(int id) {
var model = service.GetProductCategoryViewModel(id);
if (category == null) return NotFound(); //
return View(model);
}
メソッド内でGetProductCategoryViewModel
、さまざまな部分を返すプライベート メソッドを呼び出して、それらを ViewModel として組み立てることができます。
これは悪いことです。これで、私のサービスはビューモデルについて認識しました...修正しましょう。
インターフェイスを作成します。このインターフェイスは、このメソッドが返すものの実際のコントラクトです。
ICategoryWithProductsAndChildsIds // quite verbose, i know.
これで、ViewModel を次のように宣言するだけで済みます。
public class CategoryViewModel : ICategoryWithProductsAndChildsIds
そして、私たちが望む方法でそれを実装します。
ICategoryBasic
インターフェースにはあまりにも多くのものがあるようIProducts
に見えますIChilds
。
したがって、別のビューモデルを実装するときは、 のみを選択できますIProducts
。これらのコントラクトを取得するためのメソッド (プライベートかどうかに関係なく) をサービスに持たせ、サービス レイヤーでピースを接着することができます。(言うは易く行うは難し)
コードが完全に機能するようになったら、ブログ投稿や github リポジトリを作成するかもしれませんが、現時点ではまだ用意していないので、ここまでです。