ASP.NET MVC +实体框架 - 如何处理DTO和ViewModel的

时间:2016-02-19 19:28:04

标签: asp.net-mvc entity-framework dto asp.net-mvc-viewmodel

我将尝试描述我在工作场所经常看到的问题,但我找不到方法或合理的解决方案。我搜索了很多关于这一点,我能找到的就是我已经实现的。场景是这样的:

我有一个使用Entity Framework的ASP.NET MVC应用程序,它遵循Repository Pattern。我将使用简单的学生/教师数据库结构来举例说明

实体

public class Student : BaseEntity
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public DateTime DateOfBirth { get; set; }
  public Teacher Tutor { get; set; }
}

public class Teacher : BaseEntity
{
  public int Id { get; set; }
  public string Name { get; set; }
  public ICollection<Student> Students { get; set; }
}

public class BaseEntity
{
  public byte State { get; set; }
  public Datetime CreateDate { get; set; }
  public Datetime UpdateDate { get; set; }
}

现在,假设我需要公开一种方法来返回学生姓名列表及其教师姓名:

var students = context.Students.Include(x => x.Teacher).ToList();

上面的查询很糟糕,我们都知道,因为它返回所有列。现在,如果我们重构这个:

var students = context.Students.Include(x => x.Teacher)
                               .Select(x => new 
                               {
                                   Name = x.Name,
                                   TeacherName = x.Teacher.Name
                               }).ToList();

我将有一个高性能查询,只选择我想要的字段。非常好。但现在我需要做出选择。要将此列表返回给我的控制器,我可以:创建StudentDTO,或仅使用我在查询中选择的列填充Student实例。

DTO方法

如果我创建一个DTO并将其传递给我的控制器,那么一切都会“很好”。不用担心额外的不必要的字段。

返回EF实体学生

如果我按原样将数据库实体返回到我的控制器,即使我只加载了我想要的字段,我将获得所有其他空字段。所以我创建了一个ViewModel来只返回我想要的数据。 (这是我们现在在大多数方法中使用的AutoMapper)

问题:

看模式?无论哪种方式,我最终都会创建一个类来映射我的回报。我大部分时间都很好,但在我现在正在工作的项目中,我们有几种方法可以公开,每种方法都有不同的返回结构。例如,如果我想返回仅包含学生ID和姓名的学生列表,该怎么办?创建另一个DTO或ViewModel?

该项目有一个由5名开发人员组成的团队,而ViewModel文件夹正在填充类。当方法返回非常具体时,我开始指示它们在控制器内返回一个匿名对象。我现在只在我确定可以在其他地方重复使用时才创建View Models。

但是这种匿名方法也变得很糟糕,因为现在我必须在几个控制器中执行此操作,我觉得我正在重复自己。

我可以使用其他方法来解决这个问题吗?当然,我所谈论的项目比这个例子复杂得多,有几个实体和非常复杂的查询。我觉得这在其他项目中发生了很多,我没有找到一个合适的方法。

2 个答案:

答案 0 :(得分:1)

我希望一年前我会偶然发现这个问题。希望这些信息可以帮助您或其他人,即使它已经有一段时间了。

你是完全正确的。你看到一个问题,你的直觉已经告诉你有一个架构问题,你是对的。

我知道这很痛苦,但在我看来,DTO方法是正确的架构。这些类是创建和使用的痛苦,我们都试图在某些时候围绕它们。但是,您应该继承基类以在对象之间共享属性以减轻您的痛苦。此外,使用第三方开源工具(如automapper)可以在数据模型和dto对象之间轻松移动数据。

开发人员总是犯这种架构错误(试图绕过DTO对象),特别是如果他们正在处理较小的软件系统而不是大型企业企业系统,而上述架构将在快速失败的同时在小型系统中永久成功

使用您的第一个示例:

var students = context.Students.Include(x => x.Teacher).ToList();

然后,您可以将学生复制到dto对象(可能是StudentDTO),然后将其传递给您的UX并将其转换为viewmodel对象。如果Student和StudentDTO相同,则它们可以从相同的基类继承,并且使用大约一行代码创建StudentDTO。

使用您的第二个例子

var students = context.Students.Include(x => x.Teacher)
                               .Select(x => new 
                               {
                                   Name = x.Name,
                                   TeacherName = x.Teacher.Name
                               }).ToList();

如果你不这样做而是将所有内容翻译成ViewModel,那么你将会得到无尽的viewModel。

然而,更重要的是,在这里,您不应该将其移动到视图模型中,因为ViewModel应该位于您的UX层中,而您可以这样做的事实本身就表明存在一个小的架构问题。 ViewModel仅适用于UX,而不适用于其他任何地方,因此请将它们保留在UX层中。

如果将视图模型移动到UX中,您会发现无法将EF对象移动到View模型中,而这会引导您降低DTO架构方法。

那你为什么不把EF模型传递给UX呢?

嗯,你可以。例如,当你只是编辑一个学生时,在简单的场景中工作得很好。这是诱人的,因为它很容易做到它的工作,所以为什么不这样做?

对于非常简单的小型系统,请做。我觉得这很好。您可以将EF模型传递给UX,将其转换为viewmodel,然后将viewmodel传递回EF。为此,您的EF模型应位于数据层和ux层之外。我通常使用一个叫做“MySoftwaremName.Common”的dll。我将所有接口,数据模型和dtos放在此层中,并从UX,服务和数据层引用该层,以便可以轻松地在架构中上下传递DTO和接口。

我的主要实际经验表明,您不直接将EF模型传递给UX的主要原因是缓存。

在小型系统中,开发人员可能无法解决这个问题,因此这可能会很好。但是在大型系统中,我们的命中率达到50,000到100,000次,每秒缓存是关键。

如果您想要缓存您的学生对象,以便在接下来的5分钟内检索它,而不会一次又一次地访问数据库,该怎么办?

您无法在ASP.NET中缓存EF模型,或者您最终会遇到系统中随机发生的难以追查的严重问题。

您可以将EF对象粘贴到缓存中,甚至可以从缓存中检索它们。但是,EF实体附加到数据库上下文对象,并且该上下文最终超出范围并被.NET收集垃圾。如果在发生这种情况后你仍然在缓存中有你的Student对象并且你检索它并尝试使用它(再次使用你自己的例子)来获得Teacher子类“Student.Teacher”

如果教师对象未加载并且懒惰加载,您将获得死亡的黄色屏幕。以下将尝试使用上下文来获取该教师,但没有一个,它会爆炸。

为了防止这一切,最好将EF对象移动到DTO中,然后您可以安全地传输此数据,缓存它等等。

这种架构中唯一的痛苦在于将DTO转换为视图模型和EF模型,正如您所说的那样。但正如我上面提到的,使用适当的基类来共享字段并使用类似AutoMapper或其他Mapping引擎的东西将帮助您快速从DTO映射到ViewModels和EF对象。

答案 1 :(得分:0)

始终创建ViewModel。 ViewModel是一个UI问题。您的实体框架模型是一个域关注点。 ViewModels并不重复使用。它们只是定义满足使用它们的View所需的内容。