【发布时间】:2010-09-17 23:03:06
【问题描述】:
我正在重构一个 MVC 项目以使其可测试。目前,控制器直接使用实体框架的上下文对象来请求所需的数据。我开始对此进行抽象,但它不起作用。最终我有了一个 IService 和一个 IRepository 抽象,但是为了描述这个问题,让我们只看一下 IRepository。许多人建议使用返回其中一些函数的接口:IQueriable<...>、IEnumerable<...>、IList<...>、SomeEntityObject、SomeDTO。然后当人们想要测试服务层时,他们可以使用一个不去数据库返回这些的类来实现接口。
问题:对实体使用 linq 我的工具集中有延迟(延迟)加载。这实际上非常有用,因为我的控制器操作函数知道视图需要哪些数据,而且我没有要求超出要求的数据。但是 linq to anythingelse 没有延迟加载。因此,当我的 IRepository 函数返回上述任何内容时,我会丢失延迟加载。我用 "GetAnything" 和 "GetAnythingDeep" 之类的函数扩展了我的界面,但这还不够:它必须更细粒度。对于相同类型的对象,这将产生大约 5-6 个函数,具体取决于我希望在结果中获得的属性。也许可以是带有一些“包含属性”参数的通用函数,但我也不喜欢那样。
最终 atm 我认为如果我想让它可测试,这将导致效率低得多或代码复杂得多。听起来不对。
顺便说一句,我正在考虑将实体模型背后的数据源更改为 xml 或某些对象数据源,因此我可以将 linq 保留为实体。我发现它不支持开箱即用......这也很可悲:这意味着实体框架意味着数据库源 - 不是一个真正有用的抽象。
具体例子:
实体对象: 文章,语言,人。关系:文章可以有 1-N 种语言和一个人(发布者)。
ViewModel 对象: ArticleDeepViewModel:包含文章的所有属性,包括语言和人物姓名(用于查看文章,不需要人物的其他属性)。
将返回此视图的控制器操作应该从某个地方获取数据。
修改前的代码:
using (var context = new Entities.Articles())
{
var article = (from a in context.Articles.Include("Languages")
where a.ID == ID
select new ViewArticleViewModel()
{
ID = a.ID,
Headline = a.Headline,
Summary = a.Summary,
Body = a.Body,
CreatedBy = a.CreatedByEntity.Name,
CreatedDate = a.CreatedDate,
Languages = (from l in context.Languages select new ViewLanguagesViewModel() { ID = l.ID, Name = l.Name, Selected = a.Languages.Contains(l) })}).Single();
this.ViewData.Model = article;
}
return View();
修改后的代码可能是这样的:
var article = ArticleService.GetArticleDeep(ID);
var viewModel = /* mapping */
this.ViewData.Model = viewModel;
return View();
问题是 GetArticleDeep 应该返回一个包含 Languages 和整个 Person 对象的 Article 对象(它不应该知道 viewmodel 只需要 Person 的名称)。到目前为止,我还为一篇文章提供了 3 个不同的视图模型。例如,如果有人想查看文章列表,则无需获取语言、正文和一些其他属性,但获取发布者的名称(位于深)。在“可测试”代码之前,控制器操作可以只包含 linq 到实体查询并使用延迟加载、包含函数、使用子查询、引用外部属性(Publisher.Name)来获取他们需要的任何数据......因此不会对数据库进行不必要的查询,也不会从数据库中传输不必要的数据。
IService 或 IRepository 接口应该提供什么来获取 3-4 个不同级别的 Article 对象或有时是这些对象的列表?
【问题讨论】:
-
您能否发布您的代码:控制器、存储库并解释您想要单元测试的部分?
-
已编辑。我希望它让它变得更好而不是更糟:)
标签: asp.net-mvc unit-testing entity-framework testing entity-framework-4