虽然我仍在为此苦苦挣扎,但我想发布作为答案,但我也接受(并希望)对此的反馈。
在示例中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,而不是实现我们自己的复合体。此外,我们可以访问所有存储库,因此我们仍然可以在我们的服务中执行我们的工作单元。
现在我们需要选择我们想要的数据,我可能不想要我的实体的所有字段。
我能看到发生这种情况的最佳位置实际上是在 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。
但是(为什么总是有一个但是??)
我们正在进行 3 次 db hits,并且由于 Find 的原因,我们正在获取所有类别字段。此外,延迟加载必须启用。不是真正的解决方案吗?
为了改善这一点,我们可以将 find 更改为 where... 但这会将 Single 或 Find 委托给 ViewModel,它还会返回一个 IQueryable<Category>,我们知道它应该是一个。
还记得我说过“我还在挣扎吗?”这主要是为什么。为了解决这个问题,我们应该从服务返回确切需要的数据(也称为......你知道的......是的!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,或者随便你怎么命名。
所以当我们实现另一个viewModel时,我们可以选择只做IProducts。
我们可以让我们的服务具有检索这些合同的方法(私有或非私有),并将这些部分粘合到服务层中。 (说起来容易做起来难)
当我编写完整的工作代码时,我可能会创建一篇博文或一个 github 存储库,但目前我还没有,所以暂时就这些了。