【问题标题】:ASP.NET MVC + Entity Framework - How to deal with DTO's and ViewModel's [closed]ASP.NET MVC + Entity Framework - 如何处理 DTO 和 ViewModel [关闭]
【发布时间】:2016-02-19 19:28:04
【问题描述】:

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

我有一个使用遵循存储库模式的实体框架的 ASP.NET MVC 应用程序。我将使用一个简单的学生/教师数据库结构来举例说明

实体

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 文件夹中塞满了类。当方法返回非常具体时,我开始指示他们在控制器内返回一个匿名对象。我现在只在确定可以在其他地方重复使用时才创建视图模型。

但是这种匿名方法也变得很糟糕,因为现在我必须在几个控制器中这样做,而且我觉得我在重复自己。

还有其他方法可以解决这个问题吗?当然,我正在谈论的项目比这个例子复杂得多,有几个实体和相当复杂的查询。我觉得这在其他项目中经常发生,我没能找到一个像样的出路。

【问题讨论】:

  • 为什么使用空字段的 DTO 方法不行?
  • 使用 DTO 我不会有空字段。如果我返回我的数据库实体,我将得到空字段。看到基类了吗?我的客户端不需要状态和其他东西。问题是每次我需要以不同的方式返回数据时,我发现继续创建新类(DTO 或 ViewModel)效率非常低。

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


【解决方案1】:

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

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

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

开发人员总是犯这种架构错误(试图绕过 DTO 对象),特别是如果他们正在开发较小的软件系统而不是大型企业系统,因为上述架构会很快失败,而在小型系统中可能会永远成功.

使用您的第一个示例:

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

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

使用你的第二个例子

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

如果您不这样做,而是将所有内容都转换为 ViewModel,那么您最终会得到无尽的 viewModel。

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

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

那么为什么不直接将 EF 模型传递给 UX?

嗯,你可以。例如,当您只是编辑一个学生时,在简单的场景中效果很好。这很诱人,因为它很容易做到,而且很有效,为什么不这样做呢?

对于非常简单的小型系统,请执行此操作。我认为这很好。您可以将 EF 模型传递给 UX,将其转换为视图模型,然后将视图模型返回给 EF。为此,您的 EF 模型应该位于您自己的层中的数据和 ux 层之外。我通常使用名为“MySoftwaremName.Common”之类的 dll。我将所有接口、数据模型和 dto 都放在这一层中,并从 UX、Service 和 Data 层引用这一层,以便 DTO 和接口可以轻松地在架构上上下传递。

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

在小型系统中,开发人员可能不会处理这个问题,所以这可能工作得很好。但是在大型系统中,我们每秒受到 50,000 到 100,000 次以上的攻击,缓存是关键。

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

您不能在 ASP.NET 中缓存 EF 模型,否则您最终会遇到严重的问题,这些问题在您的系统中随机发生,难以追踪。

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

如果该 Teacher 对象不是急切加载的并且是由 EF 延迟加载的,您将获得黄屏死机。 Ef 会尝试使用上下文去获取那个 Teacher 并且没有,它会爆炸。

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

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

【讨论】:

  • 这就是我现在基本上正在做的事情。但我从 EF 搬到了 MongoDb。答案很好。
【解决方案2】:

始终创建一个 ViewModel。 ViewModel 是一个 UI 问题。您的实体框架模型是一个领域问题。 ViewModel 不是关于重用的。它们是关于仅定义满足使用它们的视图所需的内容。

【讨论】:

  • 这是有道理的,但不能完全回答我的问题。真正的问题是我创建了很多 ViewModel 并发现这非常低效。按照目前的情况,我将在不到几个月的时间内拥有 100 个文件。不是很优雅。
  • 您提到您制作了很多小型 ViewModel,例如 Id、Some value。在那些情况下返回一个 IDictionary 作为模型,而不是为每个场景定义一个具体的模型呢?这将消除您正在创建的所有键、值类。
  • 这只是一个例子。正如我所说,我的真实项目在大多数情况下都有复杂的查询和结果。我没有少于 5 个字段的 View 模型。
  • ViewModels 应该与您的视图 1:1。如果你有太多,想想重构你的观点和行动的方法。请参阅此链接的底部关于重复的总结。 stevefenton.co.uk/2013/03/…
  • 是的,我明白了。问题是我的大部分观点都是几个实体的“组合”。创建“包罗万象”的视图模型非常困难。此外,还有几种方法可用于不同的事情,例如加载选择框。我会检查链接,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多