MVC模式 - 这是存储库/工作单元的正确方法

时间:2014-01-26 17:18:49

标签: asp.net-mvc asp.net-mvc-5 unit-of-work

我一直在涂鸦和阅读,只是想确保我采取的方法是正确的。我正在使用MVC5和EF,实现存储库和工作单元模式。

EntityModel -> <- SomeRepository
SomeRepository -> <- SomeController
SomeController -> SomeViewModel
SomeViewModel -> SomeView
SomeView -> SomeController
SomeController -> <- SomeRepository
etc ..

在控制器中,我计划使用类似AutoMapper的东西将ViewModel映射到EntityModel(反之亦然),然后可以将其传递到我的存储库/视图。

此外,通过这种方法,我不能100%确定我的业务逻辑应该去哪里。例如,如果我有一个产品的EntityModel,并且我想添加一个GetAssociatedProducts方法,那么这是针对EntityModel还是应该引入另一个层,因此EntityModel只是一个简单的数据库映射类?

ViewModel是否应该包含任何逻辑?即根据EntityModel中的值创建Dictionary以在视图上填充下拉列表?

我正在努力避免与刚开始编码相关的问题,而没有考虑这个问题的原因是什么。

注意:我也在使用Autofac实现IoC,但我认为此时并不相关(以防万一)。

1 个答案:

答案 0 :(得分:7)

嗯,你已经在想太多了。

首先,既然你特别提到了MVC,那么我要说的是你所谈论的绝大多数是而不是 MVC。 MVC代表模型 - 视图 - 控制器。从最严格的意义上讲,您的模型是您应用程序的所有业务逻辑的避风港。控制器仅将您的模型连接到您的视图,您的视图仅以可读格式将数据呈现给客户端。

尽管名称不同,ASP.NET MVC并不真正遵循MVC模式。你可以称之为微软对MVC的看法。控制器和视图非常接近地跟踪(尽管有一些非常明显和令人厌恶的流血,例如ViewBag)。但是,“模型”位的定义非常明确。由于Entity Framework是集成的,大多数都锁定到实体并称之为模型,但实体真的坏模型。它们只是(或者至少应该只是)数据库表的编程表示:实体框架从表行中获取数据并将其放入某种结构中以便于您轻松获取数据的方法。

如果你看看其他MVC实现,比如Ruby on Rails或Django,他们的“模型”更像是一个数据库支持的工厂。它不是简单地保存从数据库返回的数据的类,而是它本身是该类型数据库的网关。它可以创建自身,更新自身,查询自身及其同事等。这使您可以在C#中使用“实体”向类添加更强大的业务逻辑。因此,您可以获得真正的MVC模型的最接近的是您的域或服务层,在ASP.NET MVC中默认情况下根本没有考虑到它。

尽管如此,如果您使用Entity Framework实现存储库/工作模式单元,那么您可能犯了一个错误。实体框架已经这样做,所以你不必这样做。 DbContext是您的工作单位,每个DbSet都是一个存储库。您创建的任何存储库,以美元为单位,最终都会将您的存储库方法代理到DbSet上的方法,这是您第一次表示某些事情不对。现在,这并不是说一定数量的抽象不是一个好主意,而是采用类似服务模式的东西:轻量级和灵活的东西,真正抽象逻辑而不是仅创建一个俄罗斯套娃的代码只会使您的应用程序难以维护。

最后,您的视图模型(实际上是MVVM模式中的一部分)应该只是您的视图所需要的。如果您的视图需要下拉列表,那么您的视图模型应包含该视图。您的视图模型是否应生成,这是一个略有不同的问题,取决于所涉及的数据的复杂性。我不认为您的视图模型应该知道如何查询数据库,因此如果您需要从数据库中提取数据,那么您应该让控制器处理该数据并将其提供给视图模型。但是,如果它类似于月份列表,枚举结构,数字静态列表等,则视图模型可能适合构造该列表的逻辑。

<强>更新

不,他们实际上正在实施一个存储库。我不确定为什么世界上MSDN上的MVC文章主张提到这一点,但作为一个早期陷入同一陷阱的人,我可以从个人经验中说,而且很多其他长期的MVC开发人员会告诉你同样的,你不想真正遵循这个建议。就像我说的那样,大多数存储库方法最终都只是代理Entity Framework方法,最终你不得不为每个新实体添加大量的样板代码。而且,你越往兔子洞走得越远,恢复就越难,一旦你最终厌倦了重复的代码,就不可避免地会进行一些重大的重构。

服务模式要简单得多。对于诸如更新和删除之类的事情,可能仍然存在某些代理,其中从一个实体到另一个实体的唯一性很小,但真实的差异将在选择中看到。使用存储库,您可以在控制器中执行以下操作:

repo.Posts.Where(m => m.BlogId = blog.Id && m.PublishDate <= DateTime.Now && m.Status == PostStatus.Published).OrderByDescending(o => o.PublishDate).Take(10).ToList();

使用服务时,您可以这样做:

service.Posts.GetPublishedPostsForBlog(blog, limit: 10);

关于什么是“已发布”帖子,博客如何连接到帖子等的所有逻辑都会进入您的服务方法而不是您的控制器。另一个很大的区别是服务方法应该返回完全烘焙的数据,即列表类型而不是可查询的数据。服务的目标是准确地返回您需要的内容,而存储库的目标是提供要查询的端点。